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