diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index 711eb746f67..60aebe8b2c4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -101,10 +101,12 @@ public class FhirContext {
private static final Map ourStaticContexts = Collections.synchronizedMap(new EnumMap<>(FhirVersionEnum.class));
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirContext.class);
private final IFhirVersion myVersion;
+ private final Map> myDefaultTypeForProfile = new HashMap<>();
+ private final Set myPerformanceOptions = new HashSet<>();
+ private final Collection> myResourceTypesToScan;
private AddProfileTagEnum myAddProfileTagWhenEncoding = AddProfileTagEnum.ONLY_FOR_CUSTOM;
private volatile Map, BaseRuntimeElementDefinition>> myClassToElementDefinition = Collections.emptyMap();
private ArrayList> myCustomTypes;
- private final Map> myDefaultTypeForProfile = new HashMap<>();
private volatile Map myIdToResourceDefinition = Collections.emptyMap();
private volatile boolean myInitialized;
private volatile boolean myInitializing = false;
@@ -115,13 +117,14 @@ public class FhirContext {
private volatile INarrativeGenerator myNarrativeGenerator;
private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
private ParserOptions myParserOptions = new ParserOptions();
- private final Set myPerformanceOptions = new HashSet<>();
- private final Collection> myResourceTypesToScan;
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private IValidationSupport myValidationSupport;
private Map>> myVersionToNameToResourceType = Collections.emptyMap();
private volatile Set myResourceNames;
+ private volatile Boolean myFormatXmlSupported;
+ private volatile Boolean myFormatJsonSupported;
+ private volatile Boolean myFormatRdfSupported;
/**
* @deprecated It is recommended that you use one of the static initializer methods instead
@@ -673,6 +676,51 @@ public class FhirContext {
return !myDefaultTypeForProfile.isEmpty();
}
+ /**
+ * @return Returns true
if the XML serialization format is supported, based on the
+ * available libraries on the classpath.
+ *
+ * @since 5.4.0
+ */
+ public boolean isFormatXmlSupported() {
+ Boolean retVal = myFormatXmlSupported;
+ if (retVal == null) {
+ retVal = tryToInitParser(() -> newXmlParser());
+ myFormatXmlSupported = retVal;
+ }
+ return retVal;
+ }
+
+ /**
+ * @return Returns true
if the JSON serialization format is supported, based on the
+ * available libraries on the classpath.
+ *
+ * @since 5.4.0
+ */
+ public boolean isFormatJsonSupported() {
+ Boolean retVal = myFormatJsonSupported;
+ if (retVal == null) {
+ retVal = tryToInitParser(() -> newJsonParser());
+ myFormatJsonSupported = retVal;
+ }
+ return retVal;
+ }
+
+ /**
+ * @return Returns true
if the RDF serialization format is supported, based on the
+ * available libraries on the classpath.
+ *
+ * @since 5.4.0
+ */
+ public boolean isFormatRdfSupported() {
+ Boolean retVal = myFormatRdfSupported;
+ if (retVal == null) {
+ retVal = tryToInitParser(() -> newRDFParser());
+ myFormatRdfSupported = retVal;
+ }
+ return retVal;
+ }
+
public IVersionSpecificBundleFactory newBundleFactory() {
return myVersion.newBundleFactory(this);
}
@@ -735,7 +783,6 @@ public class FhirContext {
* Performance Note: This method is cheap to call, and may be called once for every message being processed
* without incurring any performance penalty
*
- *
*/
public IParser newRDFParser() {
return new RDFParser(this, myParserErrorHandler, Lang.TURTLE);
@@ -989,6 +1036,24 @@ public class FhirContext {
return "FhirContext[" + myVersion.getVersion().name() + "]";
}
+ // TODO KHS add the other primitive types
+ public IPrimitiveType getPrimitiveBoolean(Boolean theValue) {
+ IPrimitiveType retval = (IPrimitiveType) getElementDefinition("boolean").newInstance();
+ retval.setValue(theValue);
+ return retval;
+ }
+
+ private static boolean tryToInitParser(Runnable run) {
+ boolean retVal;
+ try {
+ run.run();
+ retVal = true;
+ } catch (Exception | NoClassDefFoundError e) {
+ retVal = false;
+ }
+ return retVal;
+ }
+
/**
* Creates and returns a new FhirContext with version {@link FhirVersionEnum#DSTU2 DSTU2}
*/
@@ -1066,11 +1131,4 @@ public class FhirContext {
}
return retVal;
}
-
- // TODO KHS add the other primitive types
- public IPrimitiveType getPrimitiveBoolean(Boolean theValue) {
- IPrimitiveType retval = (IPrimitiveType) getElementDefinition("boolean").newInstance();
- retval.setValue(theValue);
- return retval;
- }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
index 45f4f2833e5..e21040f888d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
@@ -142,16 +142,16 @@ public abstract class BaseInterceptorService impleme
@VisibleForTesting
public void unregisterAllInterceptors() {
synchronized (myRegistryMutex) {
- myAnonymousInvokers.clear();
- myGlobalInvokers.clear();
- myInterceptors.clear();
+ unregisterInterceptors(myAnonymousInvokers.values());
+ unregisterInterceptors(myGlobalInvokers.values());
+ unregisterInterceptors(myInterceptors);
}
}
@Override
public void unregisterInterceptors(@Nullable Collection> theInterceptors) {
if (theInterceptors != null) {
- theInterceptors.forEach(t -> unregisterInterceptor(t));
+ new ArrayList<>(theInterceptors).forEach(t -> unregisterInterceptor(t));
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/CacheControlDirective.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/CacheControlDirective.java
index bab5fbade0d..08dcf86d89d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/CacheControlDirective.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/CacheControlDirective.java
@@ -125,4 +125,11 @@ public class CacheControlDirective {
return this;
}
+
+ /**
+ * Convenience factory method for a no-cache directivel
+ */
+ public static CacheControlDirective noCache() {
+ return new CacheControlDirective().setNoCache(true);
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index a7604ec3a02..32701bf0419 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -85,7 +85,8 @@ public class Constants {
public static final String FORMAT_HTML = "html";
public static final String FORMAT_JSON = "json";
public static final String FORMAT_XML = "xml";
- public static final String FORMAT_TURTLE = "text/turtle";
+ public static final String CT_RDF_TURTLE_LEGACY = "text/turtle";
+ public static final String FORMAT_TURTLE = "ttl";
/**
@@ -94,6 +95,7 @@ public class Constants {
public static final Set FORMATS_HTML;
public static final String FORMATS_HTML_JSON = "html/json";
public static final String FORMATS_HTML_XML = "html/xml";
+ public static final String FORMATS_HTML_TTL = "html/turtle";
public static final String HEADER_ACCEPT = "Accept";
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
public static final String HEADER_ACCEPT_VALUE_JSON_NON_LEGACY = CT_FHIR_JSON_NEW + ";q=1.0, " + CT_FHIR_JSON + ";q=0.9";
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/EncodingEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/EncodingEnum.java
index 905e6b0b7ed..6196202a1a7 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/EncodingEnum.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/EncodingEnum.java
@@ -46,7 +46,7 @@ public enum EncodingEnum {
}
},
- RDF(Constants.CT_RDF_TURTLE, Constants.CT_RDF_TURTLE, Constants.FORMAT_TURTLE) {
+ RDF(Constants.CT_RDF_TURTLE_LEGACY, Constants.CT_RDF_TURTLE, Constants.FORMAT_TURTLE) {
@Override
public IParser newParser(FhirContext theContext) {
return theContext.newRDFParser();
@@ -114,6 +114,7 @@ public enum EncodingEnum {
ourContentTypeToEncoding.put(JSON_PLAIN_STRING, JSON);
ourContentTypeToEncoding.put(XML_PLAIN_STRING, XML);
ourContentTypeToEncoding.put(RDF_PLAIN_STRING, RDF);
+ ourContentTypeToEncoding.put(Constants.FORMAT_TURTLE, RDF);
ourContentTypeToEncodingLegacy = Collections.unmodifiableMap(ourContentTypeToEncodingLegacy);
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2521-add-revincludes-to-capabilitystatement.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2521-add-revincludes-to-capabilitystatement.yaml
new file mode 100644
index 00000000000..7db2dda5eee
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2521-add-revincludes-to-capabilitystatement.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 2521
+title: "The automatically generated CapabilityStatement for R4+ will now incude the list of supported
+ revinclude values."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2521-add-ttl-to-capabilitystatement.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2521-add-ttl-to-capabilitystatement.yaml
new file mode 100644
index 00000000000..8fb164dbb69
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2521-add-ttl-to-capabilitystatement.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 2521
+title: "The server generated CapabilityStatement now reflects whether RDF/Turtle is supported by
+ the server. In addition, the ResponseHighlightingInterceptor will now provide some TTL support."
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/job/BaseBulkItemReader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/job/BaseBulkItemReader.java
index 89bd4a345f5..9ab5e56a75d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/job/BaseBulkItemReader.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/job/BaseBulkItemReader.java
@@ -67,10 +67,10 @@ public abstract class BaseBulkItemReader implements ItemReader formats = cs
+ .getFormat()
+ .stream()
+ .map(t -> t.getCode())
+ .collect(Collectors.toList());
+ assertThat(formats.toString(), formats, containsInAnyOrder(
+ "application/fhir+xml",
+ "application/fhir+json",
+ "json",
+ "xml",
+ "html/xml",
+ "html/json"));
+ }
+
+ @Test
+ public void testCustomSearchParamsReflectedInIncludesAndRevIncludes_TargetSpecified() {
+ SearchParameter fooSp = new SearchParameter();
+ fooSp.addBase("Observation");
+ fooSp.setCode("foo");
+ fooSp.setUrl("http://acme.com/foo");
+ fooSp.setType(Enumerations.SearchParamType.REFERENCE);
+ fooSp.setTitle("FOO SP");
+ fooSp.setDescription("This is a search param!");
+ fooSp.setExpression("Observation.subject");
+ fooSp.addTarget("Patient");
+ fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
+ fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
+ mySearchParameterDao.create(fooSp);
+ mySearchParamRegistry.forceRefresh();
+
+ ourCapabilityStatementProvider.setRestResourceRevIncludesEnabled(true);
+
+ CapabilityStatement cs = myClient
+ .capabilities()
+ .ofType(CapabilityStatement.class)
+ .cacheControl(CacheControlDirective.noCache())
+ .execute();
+
+ List includes = findIncludes(cs, "Patient");
+ assertThat(includes.toString(), includes, contains("*", "Patient:general-practitioner", "Patient:link", "Patient:organization"));
+
+ includes = findIncludes(cs, "Observation");
+ assertThat(includes.toString(), includes, contains("*", "Observation:based-on", "Observation:derived-from", "Observation:device", "Observation:encounter", "Observation:focus", "Observation:foo", "Observation:has-member", "Observation:part-of", "Observation:patient", "Observation:performer", "Observation:specimen", "Observation:subject"));
+
+ List revIncludes = findRevIncludes(cs, "Patient");
+ assertThat(revIncludes.toString(), revIncludes, hasItems(
+ "Account:patient", // Standard SP reference
+ "Observation:foo", // Standard SP reference with no explicit target
+ "Provenance:entity" // Reference in custom SP
+ ));
+ assertThat(revIncludes.toString(), revIncludes, not(hasItem(
+ "CarePlan:based-on" // Standard SP reference with non-matching target
+ )));
+
+ }
+
+ @Nonnull
+ private List findIncludes(CapabilityStatement theCapabilityStatement, String theResourceName) {
+ return theCapabilityStatement
+ .getRest()
+ .stream()
+ .flatMap(t -> t.getResource().stream())
+ .filter(t -> t.getType().equals(theResourceName))
+ .flatMap(t -> t.getSearchInclude().stream())
+ .map(t -> t.getValue())
+ .collect(Collectors.toList());
+ }
+
+ @Nonnull
+ private List findRevIncludes(CapabilityStatement theCapabilityStatement, String theResourceName) {
+ return theCapabilityStatement
+ .getRest()
+ .stream()
+ .flatMap(t -> t.getResource().stream())
+ .filter(t -> t.getType().equals(theResourceName))
+ .flatMap(t -> t.getSearchRevInclude().stream())
+ .map(t -> t.getValue())
+ .collect(Collectors.toList());
+ }
+
@Test
public void testRegisteredProfilesReflected_StoredInServer() throws IOException {
StructureDefinition sd = loadResourceFromClasspath(StructureDefinition.class, "/r4/StructureDefinition-kfdrc-patient.json");
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java
index ce08b3f01b5..7dafdd74610 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java
@@ -46,6 +46,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
@@ -94,6 +95,7 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry, IResourceC
}
}
+ @Nonnull
@Override
public Map getActiveSearchParams(String theResourceName) {
requiresActiveSearchParams();
@@ -122,7 +124,7 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry, IResourceC
params.setLoadSynchronousUpTo(MAX_MANAGED_PARAM_COUNT);
IBundleProvider allSearchParamsBp = mySearchParamProvider.search(params);
- int size = allSearchParamsBp.size();
+ int size = allSearchParamsBp.sizeOrThrowNpe();
ourLog.trace("Loaded {} search params from the DB", size);
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index f488c3e7ec1..51439570c7f 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -322,14 +322,32 @@ public class RestfulServer extends HttpServlet implements IRestfulServert.getMethodBindings().stream())
+ .forEach(t->t.close());
+ myGlobalBinding
+ .getMethodBindings()
+ .forEach(t->t.close());
+ myServerBinding
+ .getMethodBindings()
+ .forEach(t->t.close());
+
}
/**
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java
index f97c5834263..c7bc35e1811 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerConfiguration.java
@@ -31,12 +31,14 @@ import ca.uhn.fhir.rest.server.method.SearchParameter;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import ca.uhn.fhir.util.VersionUtil;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -278,7 +280,7 @@ public class RestfulServerConfiguration implements ISearchParamRetriever {
}
public Map>> collectMethodBindings() {
- Map>> resourceToMethods = new TreeMap>>();
+ Map>> resourceToMethods = new TreeMap<>();
for (ResourceBinding next : getResourceBindings()) {
String resourceName = next.getResourceName();
for (BaseMethodBinding> nextMethodBinding : next.getMethodBindings()) {
@@ -365,14 +367,15 @@ public class RestfulServerConfiguration implements ISearchParamRetriever {
}
@Override
- public Map getActiveSearchParams(String theResourceName) {
+ public Map getActiveSearchParams(@Nonnull String theResourceName) {
+ Validate.notBlank(theResourceName, "theResourceName must not be null or blank");
Map retVal = new LinkedHashMap<>();
collectMethodBindings()
.getOrDefault(theResourceName, Collections.emptyList())
.stream()
- .filter(t -> t.getResourceName().equals(theResourceName))
+ .filter(t -> theResourceName.equals(t.getResourceName()))
.filter(t -> t instanceof SearchMethodBinding)
.map(t -> (SearchMethodBinding) t)
.filter(t -> t.getQueryName() == null)
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java
index a9fb72e69b0..fef13edab8f 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java
@@ -18,23 +18,35 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
+import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.hl7.fhir.instance.model.api.IBaseBinary;
+import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
-import java.util.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.defaultString;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.trim;
/*
* #%L
@@ -71,10 +83,10 @@ public class ResponseHighlighterInterceptor {
*/
public static final String PARAM_RAW = "_raw";
public static final String PARAM_RAW_TRUE = "true";
- public static final String PARAM_TRUE = "true";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseHighlighterInterceptor.class);
private static final String[] PARAM_FORMAT_VALUE_JSON = new String[]{Constants.FORMAT_JSON};
private static final String[] PARAM_FORMAT_VALUE_XML = new String[]{Constants.FORMAT_XML};
+ private static final String[] PARAM_FORMAT_VALUE_TTL = new String[]{Constants.FORMAT_TURTLE};
private boolean myShowRequestHeaders = false;
private boolean myShowResponseHeaders = true;
@@ -128,6 +140,9 @@ public class ResponseHighlighterInterceptor {
boolean inValue = false;
boolean inQuote = false;
boolean inTag = false;
+ boolean inTurtleDirective = false;
+ boolean startingLineNext = true;
+ boolean startingLine = false;
int lineCount = 1;
for (int i = 0; i < str.length(); i++) {
@@ -140,13 +155,23 @@ public class ResponseHighlighterInterceptor {
char nextChar6 = (i + 5) < str.length() ? str.charAt(i + 5) : ' ';
if (nextChar == '\n') {
+ if (inTurtleDirective) {
+ theTarget.append("");
+ inTurtleDirective = false;
+ }
lineCount++;
theTarget.append("");
+ startingLineNext = true;
continue;
+ } else if (startingLineNext) {
+ startingLineNext = false;
+ startingLine = true;
+ } else {
+ startingLine = false;
}
if (theEncodingEnum == EncodingEnum.JSON) {
@@ -194,7 +219,45 @@ public class ResponseHighlighterInterceptor {
}
}
+ } else if (theEncodingEnum == EncodingEnum.RDF) {
+
+ if (inQuote) {
+ theTarget.append(nextChar);
+ if (prevChar != '\\' && nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
+ theTarget.append("quot;");
+ i += 5;
+ inQuote = false;
+ } else if (nextChar == '\\' && nextChar2 == '"') {
+ theTarget.append("quot;");
+ i += 5;
+ inQuote = false;
+ }
+ } else if (startingLine && nextChar == '@') {
+ inTurtleDirective = true;
+ theTarget.append("
");
+ theTarget.append(nextChar);
+ } else if (startingLine) {
+ inTurtleDirective = true;
+ theTarget.append("");
+ theTarget.append(nextChar);
+ } else if (nextChar == '[' || nextChar == ']' || nextChar == ';' || nextChar == ':') {
+ theTarget.append("");
+ theTarget.append(nextChar);
+ theTarget.append("");
+ } else {
+ if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
+ theTarget.append(""");
+ inQuote = true;
+ i += 5;
+ } else {
+ theTarget.append(nextChar);
+ }
+ }
+
} else {
+
+ // Ok it's XML
+
if (inQuote) {
theTarget.append(nextChar);
if (nextChar == '&' && nextChar2 == 'q' && nextChar3 == 'u' && nextChar4 == 'o' && nextChar5 == 't' && nextChar6 == ';') {
@@ -345,6 +408,26 @@ public class ResponseHighlighterInterceptor {
return false;
}
+ @Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED)
+ public void capabilityStatementGenerated(RequestDetails theRequestDetails, IBaseConformance theCapabilityStatement) {
+ FhirTerser terser = theRequestDetails.getFhirContext().newTerser();
+
+ Set formats = terser.getValues(theCapabilityStatement, "format", IPrimitiveType.class)
+ .stream()
+ .map(t -> t.getValueAsString())
+ .collect(Collectors.toSet());
+ addFormatConditionally(theCapabilityStatement, terser, formats, Constants.CT_FHIR_JSON_NEW, Constants.FORMATS_HTML_JSON);
+ addFormatConditionally(theCapabilityStatement, terser, formats, Constants.CT_FHIR_XML_NEW, Constants.FORMATS_HTML_XML);
+ addFormatConditionally(theCapabilityStatement, terser, formats, Constants.CT_RDF_TURTLE, Constants.FORMATS_HTML_TTL);
+ }
+
+ private void addFormatConditionally(IBaseConformance theCapabilityStatement, FhirTerser terser, Set formats, String wanted, String toAdd) {
+ if (formats.contains(wanted)) {
+ terser.addElement(theCapabilityStatement, "format", toAdd);
+ }
+ }
+
+
private boolean handleOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse, String theGraphqlResponse, IBaseResource theResourceResponse) {
/*
* Request for _raw
@@ -373,6 +456,9 @@ public class ResponseHighlighterInterceptor {
} else if (Constants.FORMATS_HTML_JSON.equals(formatParam)) {
force = true;
theRequestDetails.addParameter(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_JSON);
+ } else if (Constants.FORMATS_HTML_TTL.equals(formatParam)) {
+ force = true;
+ theRequestDetails.addParameter(Constants.PARAM_FORMAT, PARAM_FORMAT_VALUE_TTL);
} else {
return true;
}
@@ -542,8 +628,6 @@ public class ResponseHighlighterInterceptor {
outputBuffer.append(" position: relative;\n");
outputBuffer.append("}");
outputBuffer.append(".responseBodyTableFirstColumn {");
-// outputBuffer.append(" position: absolute;\n");
-// outputBuffer.append(" width: 70px;\n");
outputBuffer.append("}");
outputBuffer.append(".responseBodyTableSecondColumn {");
outputBuffer.append(" position: absolute;\n");
@@ -589,24 +673,47 @@ public class ResponseHighlighterInterceptor {
outputBuffer.append("This result is being rendered in HTML for easy viewing. ");
outputBuffer.append("You may access this content as ");
- outputBuffer.append("Raw JSON or ");
+ if (theRequestDetails.getFhirContext().isFormatJsonSupported()) {
+ outputBuffer.append("Raw JSON or ");
+ }
- outputBuffer.append("Raw XML, ");
+ if (theRequestDetails.getFhirContext().isFormatXmlSupported()) {
+ outputBuffer.append("Raw XML or ");
+ }
- outputBuffer.append(" or view this content in ");
+ if (theRequestDetails.getFhirContext().isFormatRdfSupported()) {
+ outputBuffer.append("Raw Turtle or ");
+ }
- outputBuffer.append("HTML JSON ");
+ outputBuffer.append("view this content in ");
- outputBuffer.append("or ");
- outputBuffer.append("HTML XML.");
+ if (theRequestDetails.getFhirContext().isFormatJsonSupported()) {
+ outputBuffer.append("HTML JSON ");
+ }
+
+ if (theRequestDetails.getFhirContext().isFormatXmlSupported()) {
+ outputBuffer.append("or ");
+ outputBuffer.append("HTML XML ");
+ }
+
+ if (theRequestDetails.getFhirContext().isFormatRdfSupported()) {
+ outputBuffer.append("or ");
+ outputBuffer.append("HTML Turtle ");
+ }
+
+ outputBuffer.append(".");
}
Date startTime = (Date) theServletRequest.getAttribute(RestfulServer.REQUEST_START_TIME);
@@ -680,7 +787,7 @@ public class ResponseHighlighterInterceptor {
outputBuffer.append("\n");
InputStream jsStream = ResponseHighlighterInterceptor.class.getResourceAsStream("ResponseHighlighter.js");
- String jsStr = jsStream != null ? IOUtils.toString(jsStream, "UTF-8") : "console.log('ResponseHighlighterInterceptor: javascript theResource not found')";
+ String jsStr = jsStream != null ? IOUtils.toString(jsStream, StandardCharsets.UTF_8) : "console.log('ResponseHighlighterInterceptor: javascript theResource not found')";
jsStr = jsStr.replace("FHIR_BASE", theRequestDetails.getServerBaseForRequest());
outputBuffer.append("