001package org.hl7.fhir.r5.openapi;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.util.HashSet;
034import java.util.List;
035import java.util.Set;
036
037import org.hl7.fhir.r5.context.IWorkerContext;
038import org.hl7.fhir.r5.model.CapabilityStatement;
039import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
040import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
041import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
042import org.hl7.fhir.r5.model.CapabilityStatement.ResourceInteractionComponent;
043import org.hl7.fhir.r5.model.CapabilityStatement.ResourceVersionPolicy;
044import org.hl7.fhir.r5.model.CapabilityStatement.SystemInteractionComponent;
045import org.hl7.fhir.r5.model.CapabilityStatement.SystemRestfulInteraction;
046import org.hl7.fhir.r5.model.CapabilityStatement.TypeRestfulInteraction;
047import org.hl7.fhir.r5.model.CodeType;
048import org.hl7.fhir.r5.model.ContactDetail;
049import org.hl7.fhir.r5.model.ContactPoint;
050import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
051import org.hl7.fhir.r5.model.Enumerations.RestfulCapabilityMode;
052import org.hl7.fhir.r5.model.Enumerations.SearchParamType;
053import org.hl7.fhir.r5.model.SearchParameter;
054import org.hl7.fhir.r5.openapi.ParameterWriter.ParameterLocation;
055import org.hl7.fhir.r5.openapi.ParameterWriter.ParameterStyle;
056import org.hl7.fhir.r5.openapi.SchemaWriter.SchemaType;
057import org.hl7.fhir.utilities.Utilities;
058
059
060public class OpenApiGenerator {
061
062  private IWorkerContext context;
063  private CapabilityStatement source;
064  private Writer dest;
065
066  public OpenApiGenerator(IWorkerContext context, CapabilityStatement cs, Writer oa) {
067    this.context = context;
068    this.source = cs;
069    this.dest = oa;
070  }
071
072  public void generate(String license, String url) {
073    dest.info().title(source.present()).description(source.getDescription()).license(license, url).version(source.getVersion());
074    for (ContactDetail cd : source.getContact()) {
075      dest.info().contact(cd.getName(), email(cd.getTelecom()), url(cd.getTelecom()));
076    }
077    if (source.hasPublisher())
078      dest.info().contact(source.getPublisher(), null, null);
079
080    if (source.hasImplementation()) {
081      dest.server(source.getImplementation().getUrl()).description(source.getImplementation().getDescription());
082    }
083    dest.externalDocs().url(source.getUrl()).description("FHIR CapabilityStatement");
084
085    for (CapabilityStatementRestComponent csr : source.getRest()) {
086      if (csr.getMode() == RestfulCapabilityMode.SERVER) {
087        generatePaths(csr);
088      }
089    }
090    writeBaseParameters(dest.components());
091  }
092
093  private void writeBaseParameters(ComponentsWriter components) {
094    components.parameter("rid").name("rid").in(ParameterLocation.path).description("id of the resource (=Resource.id)").required(true).allowEmptyValue(false).style(ParameterStyle.simple)
095    .schema().type(SchemaType.string);
096    
097    components.parameter("hid").name("hid").in(ParameterLocation.path).description("id of the history entry (=Resource.meta.versionId)").required(true).allowEmptyValue(false).style(ParameterStyle.simple)
098    .schema().type(SchemaType.string);
099
100    components.parameter("summary").name("_summary").in(ParameterLocation.query).description("Requests the server to return a designated subset of the resource").allowEmptyValue().style(ParameterStyle.form)
101    .schema().type(SchemaType.string).enums("true", "text", "data", "count", "false");
102
103    components.parameter("format").name("_format").in(ParameterLocation.query).description("Specify alternative response formats by their MIME-types (when a client is unable acccess accept: header)").allowEmptyValue().style(ParameterStyle.form)
104    .schema().type(SchemaType.string).format("mime-type");
105
106    components.parameter("pretty").name("_pretty").in(ParameterLocation.query).description("Ask for a pretty printed response for human convenience").allowEmptyValue().style(ParameterStyle.form)
107    .schema().type(SchemaType.bool);
108
109    SchemaWriter p = components.parameter("elements").name("_elements").in(ParameterLocation.query).description("Requests the server to return a collection of elements from the resource").allowEmptyValue().style(ParameterStyle.form).explode(false)
110    .schema();
111    p.type(SchemaType.array).format("string");
112    p.items().format("string");
113
114    components.parameter("count").name("_count").in(ParameterLocation.query).description("The maximum number of search results on a page. The server is not bound to return the number requested, but cannot return more")
115    .schema().type(SchemaType.number);
116  }
117
118  private void generatePaths(CapabilityStatementRestComponent csr) {
119    generateMetadata();
120    for (CapabilityStatementRestResourceComponent r : csr.getResource())
121      generateResource(r);
122    if (hasOp(csr, SystemRestfulInteraction.HISTORYSYSTEM))
123      generateHistorySystem(csr);
124    if (hasOp(csr, SystemRestfulInteraction.SEARCHSYSTEM))
125      generateSearchSystem(csr);
126    if (hasOp(csr, SystemRestfulInteraction.BATCH) || hasOp(csr, SystemRestfulInteraction.TRANSACTION) )
127      generateBatchTransaction(csr);
128  }
129
130  private void generateResource(CapabilityStatementRestResourceComponent r) {
131    if (hasOp(r, TypeRestfulInteraction.SEARCHTYPE)) 
132      generateSearch(r);
133    if (hasOp(r, TypeRestfulInteraction.READ))
134      generateRead(r);
135    if (hasOp(r, TypeRestfulInteraction.CREATE)) 
136      generateCreate(r);
137    if (hasOp(r, TypeRestfulInteraction.UPDATE)) 
138      generateUpdate(r);
139    if (hasOp(r, TypeRestfulInteraction.PATCH)) 
140      generatePatch(r);
141    if (hasOp(r, TypeRestfulInteraction.DELETE)) 
142      generateDelete(r);
143    if (hasOp(r, TypeRestfulInteraction.HISTORYINSTANCE)) 
144      generateHistoryInstance(r);
145    if (hasOp(r, TypeRestfulInteraction.VREAD)) 
146      generateVRead(r);
147    if (hasOp(r, TypeRestfulInteraction.HISTORYTYPE)) 
148      generateHistoryType(r);
149  }
150
151  private void generateMetadata() {
152    OperationWriter op = makePathMetadata().operation("get");
153    op.summary("Return the server's capability statement");
154    op.operationId("metadata");
155    opOutcome(op.responses().defaultResponse());
156    ResponseObjectWriter resp = op.responses().httpResponse("200");
157    resp.description("the capbility statement");
158    if (isJson())
159      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/CapabilityStatement");
160    if (isXml())
161      resp.content("application/fhir+xml").schemaRef(specRef()+"/CapabilityStatement.xsd");
162
163    // parameters - but do they apply?
164    op.paramRef("#/components/parameters/format");
165    op.paramRef("#/components/parameters/pretty");
166    op.paramRef("#/components/parameters/summary");
167    op.paramRef("#/components/parameters/elements");
168  }
169
170  private void generateRead(CapabilityStatementRestResourceComponent r) {
171    OperationWriter op = makePathResId(r).operation("get");
172    op.summary("Read the current state of the resource");
173    op.operationId("read"+r.getType());
174    opOutcome(op.responses().defaultResponse());
175    ResponseObjectWriter resp = op.responses().httpResponse("200");
176    resp.description("the resource being returned");
177    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
178      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
179    if (isJson())
180      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
181    if (isXml())
182      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
183
184    // parameters:
185    op.paramRef("#/components/parameters/rid");
186    op.paramRef("#/components/parameters/summary");
187    op.paramRef("#/components/parameters/format");
188    op.paramRef("#/components/parameters/pretty");
189    op.paramRef("#/components/parameters/elements");
190  }
191
192  private void generateSearch(CapabilityStatementRestResourceComponent r) {
193    OperationWriter op = makePathResType(r).operation("get");
194    op.summary("Search all resources of type "+r.getType()+" based on a set of criteria");
195    op.operationId("search"+r.getType());
196    opOutcome(op.responses().defaultResponse());
197    ResponseObjectWriter resp = op.responses().httpResponse("200");
198    resp.description("the resource being returned");
199    if (isJson())
200      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
201    if (isXml())
202      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
203    // todo: how do we know that these apply? 
204    op.paramRef("#/components/parameters/format");
205    op.paramRef("#/components/parameters/pretty");
206    op.paramRef("#/components/parameters/summary");
207    op.paramRef("#/components/parameters/elements");
208    Set<String> set = new HashSet<>();
209    for (CapabilityStatementRestResourceSearchParamComponent spc : r.getSearchParam()) {
210      if (!set.contains(spc.getName())) {
211        set.add(spc.getName());
212        ParameterWriter p = op.parameter(spc.getName());
213        p.in(ParameterLocation.query).description(spc.getDocumentation());
214        p.schema().type(getSchemaType(spc.getType()));
215        if (spc.hasDefinition()) {
216          SearchParameter sp = context.fetchResource(SearchParameter.class, spc.getDefinition());
217          if (sp != null) {
218            p.description(sp.getDescription());
219          }
220        }
221      }
222    }
223  }
224
225  private void generateSearchSystem(CapabilityStatementRestComponent csr) {
226    OperationWriter op = makePathSystem().operation("get");
227    op.summary("Search all resources of all types based on a set of criteria");
228    op.operationId("searchAll");
229    opOutcome(op.responses().defaultResponse());
230    ResponseObjectWriter resp = op.responses().httpResponse("200");
231    resp.description("the resource being returned");
232    if (isJson())
233      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
234    if (isXml())
235      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
236    // todo: how do we know that these apply? 
237    op.paramRef("#/components/parameters/format");
238    op.paramRef("#/components/parameters/pretty");
239    op.paramRef("#/components/parameters/summary");
240    op.paramRef("#/components/parameters/elements");
241    Set<String> set = new HashSet<>();
242    set.add("_summary");
243    set.add("_format");
244    set.add("_pretty");
245    set.add("_elements");
246    for (CapabilityStatementRestResourceSearchParamComponent spc : csr.getSearchParam()) {
247      if (!set.contains(spc.getName())) {
248        set.add(spc.getName());
249        ParameterWriter p = op.parameter(spc.getName());
250        p.in(ParameterLocation.query).description(spc.getDocumentation());
251        p.schema().type(getSchemaType(spc.getType()));
252        if (spc.hasDefinition()) {
253          SearchParameter sp = context.fetchResource(SearchParameter.class, spc.getDefinition());
254          if (sp != null) {
255            p.description(sp.getDescription());
256          }
257        }
258      }
259    }
260  }
261
262  private SchemaType getSchemaType(SearchParamType type) {
263    switch (type) {
264    // case COMPOSITE:
265    case DATE: return SchemaType.dateTime;
266    case NUMBER: return SchemaType.number; 
267    case QUANTITY: return SchemaType.string;
268    case REFERENCE: return SchemaType.string;
269    case STRING: return SchemaType.string;
270    case TOKEN: return SchemaType.string;
271    case URI: return SchemaType.string;
272    }
273    return null;
274  }
275
276  private void generateHistoryType(CapabilityStatementRestResourceComponent r) {
277    OperationWriter op = makePathResHistListType(r).operation("get");
278    op.summary("Read the past states of the resource");
279    op.operationId("histtype"+r.getType());
280    opOutcome(op.responses().defaultResponse());
281    ResponseObjectWriter resp = op.responses().httpResponse("200");
282    resp.description("the resources being returned");
283    if (isJson())
284      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
285    if (isXml())
286      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
287    op.paramRef("#/components/parameters/summary");
288    op.paramRef("#/components/parameters/format");
289    op.paramRef("#/components/parameters/pretty");
290    op.paramRef("#/components/parameters/elements");
291    op.paramRef("#/components/parameters/count");
292
293    op.parameter("_since").in(ParameterLocation.query).description("Only include resource versions that were created at or after the given instant in time").schema().type(SchemaType.dateTime);
294    op.parameter("_at").in(ParameterLocation.query).description("Only include resource versions that were current at some point during the time period specified in the date time value (see Search notes on date searching)").schema().type(SchemaType.dateTime);
295    op.parameter("_list").in(ParameterLocation.query).description("Only include resource versions that are referenced in the specified list (current list references are allowed)").schema().type(SchemaType.string);
296  }
297
298  private void generateHistoryInstance(CapabilityStatementRestResourceComponent r) {
299    OperationWriter op = makePathResHistListId(r).operation("get");
300    op.summary("Read the past states of the resource");
301    op.operationId("histinst"+r.getType());
302    opOutcome(op.responses().defaultResponse());
303    ResponseObjectWriter resp = op.responses().httpResponse("200");
304    resp.description("the resources being returned");
305    if (isJson())
306      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
307    if (isXml())
308      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
309    op.paramRef("#/components/parameters/rid");
310    op.paramRef("#/components/parameters/summary");
311    op.paramRef("#/components/parameters/format");
312    op.paramRef("#/components/parameters/pretty");
313    op.paramRef("#/components/parameters/elements");
314    op.paramRef("#/components/parameters/count");
315
316    op.parameter("_since").in(ParameterLocation.query).description("Only include resource versions that were created at or after the given instant in time").schema().type(SchemaType.dateTime);
317    op.parameter("_at").in(ParameterLocation.query).description("Only include resource versions that were current at some point during the time period specified in the date time value (see Search notes on date searching)").schema().type(SchemaType.dateTime);
318    op.parameter("_list").in(ParameterLocation.query).description("Only include resource versions that are referenced in the specified list (current list references are allowed)").schema().type(SchemaType.string);
319  }
320
321  private void generateHistorySystem(CapabilityStatementRestComponent csr) {
322    OperationWriter op = makePathHistListSystem().operation("get");
323    op.summary("Read the past states of all resources");
324    op.operationId("histAll");
325    opOutcome(op.responses().defaultResponse());
326    ResponseObjectWriter resp = op.responses().httpResponse("200");
327    resp.description("the resources being returned");
328    if (isJson())
329      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
330    if (isXml())
331      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
332    op.paramRef("#/components/parameters/summary");
333    op.paramRef("#/components/parameters/format");
334    op.paramRef("#/components/parameters/pretty");
335    op.paramRef("#/components/parameters/elements");
336    op.paramRef("#/components/parameters/count");
337
338    op.parameter("_since").in(ParameterLocation.query).description("Only include resource versions that were created at or after the given instant in time").schema().type(SchemaType.dateTime);
339    op.parameter("_at").in(ParameterLocation.query).description("Only include resource versions that were current at some point during the time period specified in the date time value (see Search notes on date searching)").schema().type(SchemaType.dateTime);
340    op.parameter("_list").in(ParameterLocation.query).description("Only include resource versions that are referenced in the specified list (current list references are allowed)").schema().type(SchemaType.string);
341  }
342
343  private void generateVRead(CapabilityStatementRestResourceComponent r) {
344    OperationWriter op = makePathResHistId(r).operation("get");
345    op.summary("Read a past state of the resource");
346    op.operationId("vread"+r.getType());
347    opOutcome(op.responses().defaultResponse());
348    ResponseObjectWriter resp = op.responses().httpResponse("200");
349    resp.description("the resource being returned");
350    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
351      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag for that version").schema().type(SchemaType.string);
352    if (isJson())
353      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
354    if (isXml())
355      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
356    op.paramRef("#/components/parameters/rid");
357    op.paramRef("#/components/parameters/hid");
358    op.paramRef("#/components/parameters/summary");
359    op.paramRef("#/components/parameters/format");
360    op.paramRef("#/components/parameters/pretty");
361    op.paramRef("#/components/parameters/elements");
362  }
363
364  // todo: how does prefer header affect return type?
365  private void generateUpdate(CapabilityStatementRestResourceComponent r) {
366    OperationWriter op = makePathResId(r).operation("put");
367    if (r.getUpdateCreate())
368      op.summary("Update the current state of the resource (can create a new resource if it does not exist)");
369    else
370      op.summary("Update the current state of the resource");
371    op.operationId("update"+r.getType());
372    RequestBodyWriter req = op.request();
373    req.description("The new state of the resource").required(true);
374    if (isJson())
375      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
376    if (isXml())
377      req.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
378
379    opOutcome(op.responses().defaultResponse());
380    ResponseObjectWriter resp = op.responses().httpResponse("200");
381    resp.description("the resource being returned after being updated");
382    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
383      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
384    if (isJson())
385      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
386    if (isXml())
387      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
388    op.paramRef("#/components/parameters/rid");
389    op.paramRef("#/components/parameters/summary");
390    op.paramRef("#/components/parameters/format");
391    op.paramRef("#/components/parameters/pretty");
392    op.paramRef("#/components/parameters/elements");
393  }
394
395  private void generatePatch(CapabilityStatementRestResourceComponent r) {
396    OperationWriter op = makePathResId(r).operation("patch");
397    op.summary("Change the current state of the resource by providing a patch - a series of change commands");
398    op.operationId("patch"+r.getType());
399    RequestBodyWriter req = op.request();
400    req.description("The new state of the resource").required(true);
401    if (isJson()) {
402      req.content("application/json-patch+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
403      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Parameters");
404    }
405    if (isXml()) {
406      req.content("application/xml-patch+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
407      req.content("application/fhir+xml").schemaRef(specRef()+"/Parameters.xsd");
408    }
409
410    opOutcome(op.responses().defaultResponse());
411    ResponseObjectWriter resp = op.responses().httpResponse("200");
412    resp.description("the resource being returned after being patched");
413    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
414      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
415    if (isJson())
416      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
417    if (isXml())
418      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
419    op.paramRef("#/components/parameters/rid");
420    op.paramRef("#/components/parameters/summary");
421    op.paramRef("#/components/parameters/format");
422    op.paramRef("#/components/parameters/pretty");
423    op.paramRef("#/components/parameters/elements");
424  }
425
426  private void generateDelete(CapabilityStatementRestResourceComponent r) {
427    OperationWriter op = makePathResId(r).operation("delete");
428    op.summary("Delete the resource so that it no exists (no read, search etc)");
429    op.operationId("delete"+r.getType());
430    opOutcome(op.responses().defaultResponse());
431    ResponseObjectWriter resp = op.responses().httpResponse("204");
432    resp.description("If the resource is deleted - no content is returned");
433    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
434      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
435    op.paramRef("#/components/parameters/rid");
436  }
437
438  private void generateCreate(CapabilityStatementRestResourceComponent r) {
439    OperationWriter op = makePathRes(r).operation("post");
440    op.summary("Create a new resource");
441    op.operationId("create"+r.getType());
442    RequestBodyWriter req = op.request();
443    req.description("The new state of the resource").required(true);
444    if (isJson())
445      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
446    if (isXml())
447      req.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
448
449    opOutcome(op.responses().defaultResponse());
450    ResponseObjectWriter resp = op.responses().httpResponse("200");
451    resp.description("the resource being returned after being updated");
452    if (r.getVersioning() != ResourceVersionPolicy.NOVERSION)
453      resp.header("ETag").description("Version from Resource.meta.version as a weak ETag").schema().type(SchemaType.string);
454    if (isJson())
455      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/"+r.getType());
456    if (isXml())
457      resp.content("application/fhir+xml").schemaRef(specRef()+"/"+r.getType()+".xsd");
458    op.paramRef("#/components/parameters/summary");
459    op.paramRef("#/components/parameters/format");
460    op.paramRef("#/components/parameters/pretty");
461    op.paramRef("#/components/parameters/elements");
462  }
463
464  private void generateBatchTransaction(CapabilityStatementRestComponent csr) {
465    OperationWriter op = makePathSystem().operation("put");
466    op.summary("Batch or Transaction");
467    op.operationId("transaction");
468    RequestBodyWriter req = op.request();
469    req.description("The batch or transaction").required(true);
470    if (isJson())
471      req.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
472    if (isXml())
473      req.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
474
475    opOutcome(op.responses().defaultResponse());
476    ResponseObjectWriter resp = op.responses().httpResponse("200");
477    resp.description("Batch or Transaction response");
478    if (isJson())
479      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/Bundle");
480    if (isXml())
481      resp.content("application/fhir+xml").schemaRef(specRef()+"/Bundle.xsd");
482    op.paramRef("#/components/parameters/format");
483    op.paramRef("#/components/parameters/pretty");
484  }
485
486  private void opOutcome(ResponseObjectWriter resp) {
487    resp.description("Error, with details");
488    if (isJson())
489      resp.content("application/fhir+json").schemaRef(specRef()+"/fhir.schema.json#/definitions/OperationOutcome");
490    if (isXml())
491      resp.content("application/fhir+xml").schemaRef(specRef()+"/OperationOutcome.xsd");    
492  }
493
494  private String specRef() {
495    String ver = context.getVersion();
496    if (Utilities.noString(ver))
497      return "https://hl7.org/fhir/STU3";
498    if (ver.startsWith("4.0"))
499      return "https://hl7.org/fhir/R4";
500    if (ver.startsWith("3.0"))
501      return "https://hl7.org/fhir/STU3";
502    if (ver.startsWith("1.0"))
503      return "https://hl7.org/fhir/DSTU2";
504    if (ver.startsWith("1.4"))
505      return "https://hl7.org/fhir/2016May";
506    return "https://build.fhir.org";    
507  }
508
509  private boolean isJson() {
510    for (CodeType f : source.getFormat()) {
511      if (f.getCode().contains("json"))
512        return true;
513    }
514    return false;
515  }
516
517  private boolean isXml() {
518    for (CodeType f : source.getFormat()) {
519      if (f.getCode().contains("xml"))
520        return true;
521    }
522    return false;
523  }
524
525  public PathItemWriter makePathSystem() {
526    PathItemWriter p = dest.path("/");
527    p.summary("System level operations");
528    p.description("System level operations");
529    return p;
530  }
531
532  public PathItemWriter makePathMetadata() {
533    PathItemWriter p = dest.path("/metadata");
534    p.summary("Access to the Server's Capability Statement");
535    p.description("All FHIR Servers return a CapabilityStatement that describes what services they perform");
536    return p;
537  }
538
539  public PathItemWriter makePathRes(CapabilityStatementRestResourceComponent r) {
540    PathItemWriter p = dest.path("/"+r.getType());
541    p.summary("Manager for resources of type "+r.getType());
542    p.description("The Manager for resources of type "+r.getType()+": provides services to manage the collection of all the "+r.getType()+" instances");
543    return p;
544  }
545
546  public PathItemWriter makePathResId(CapabilityStatementRestResourceComponent r) {
547    PathItemWriter p = dest.path("/"+r.getType()+"/{rid}");
548    p.summary("Read/Write/etc resource instance of type "+r.getType());
549    p.description("Access to services to manage the state of a single resource of type "+r.getType());
550    return p;
551  }
552
553  public PathItemWriter makePathResType(CapabilityStatementRestResourceComponent r) {
554    PathItemWriter p = dest.path("/"+r.getType());
555    p.summary("manage the collection of resources of type "+r.getType());
556    p.description("Access to services to manage the collection of all resources of type "+r.getType());
557    return p;
558  }
559
560  public PathItemWriter makePathResHistListType(CapabilityStatementRestResourceComponent r) {
561    PathItemWriter p = dest.path("/"+r.getType()+"/_history");
562    p.summary("Read past versions of resources of type "+r.getType());
563    p.description("Access to previous versions of resourcez of type "+r.getType());
564    return p;
565  }
566
567  public PathItemWriter makePathResHistListId(CapabilityStatementRestResourceComponent r) {
568    PathItemWriter p = dest.path("/"+r.getType()+"/{rid}/_history");
569    p.summary("Read past versions of resource instance of type "+r.getType());
570    p.description("Access to previous versions of a single resource of type "+r.getType());
571    return p;
572  }
573
574  public PathItemWriter makePathResHistId(CapabilityStatementRestResourceComponent r) {
575    PathItemWriter p = dest.path("/"+r.getType()+"/{rid}/_history/{hid}");
576    p.summary("Read a past version of resource instance of type "+r.getType());
577    p.description("Access a to specified previous version of a single resource of type "+r.getType());
578    return p;
579  }
580
581  public PathItemWriter makePathHistListSystem() {
582    PathItemWriter p = dest.path("/_history");
583    p.summary("Read a past version of resource instance of all types");
584    p.description("Access a previous versions of all types");
585    return p;
586  }
587
588  private boolean hasOp(CapabilityStatementRestComponent r, SystemRestfulInteraction opCode) {
589    for (SystemInteractionComponent op : r.getInteraction()) {
590      if (op.getCode() == opCode) 
591        return true;
592    }
593    return false;
594  }
595
596  private boolean hasOp(CapabilityStatementRestResourceComponent r, TypeRestfulInteraction opCode) {
597    for (ResourceInteractionComponent op : r.getInteraction()) {
598      if (op.getCode() == opCode) 
599        return true;
600    }
601    return false;
602  }
603
604  private String url(List<ContactPoint> telecom) {
605    for (ContactPoint cp : telecom) {
606      if (cp.getSystem() == ContactPointSystem.URL)
607        return cp.getValue();
608    }
609    return null;
610  }
611
612
613  private String email(List<ContactPoint> telecom) {
614    for (ContactPoint cp : telecom) {
615      if (cp.getSystem() == ContactPointSystem.EMAIL)
616        return cp.getValue();
617    }
618    return null;
619  }
620
621
622
623}