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