001package ca.uhn.fhir.jaxrs.server; 002 003/* 004 * #%L 005 * HAPI FHIR JAX-RS Server 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import java.io.IOException; 024import java.lang.annotation.Annotation; 025import java.lang.reflect.Method; 026import java.lang.reflect.Modifier; 027import java.util.*; 028import java.util.Map.Entry; 029import java.util.concurrent.ConcurrentHashMap; 030 031import javax.annotation.PostConstruct; 032import javax.ws.rs.*; 033import javax.ws.rs.core.MediaType; 034import javax.ws.rs.core.Response; 035 036import ca.uhn.fhir.interceptor.api.IInterceptorService; 037import org.apache.commons.lang3.StringUtils; 038import org.hl7.fhir.instance.model.api.IBaseResource; 039import org.slf4j.LoggerFactory; 040 041import ca.uhn.fhir.context.*; 042import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; 043import ca.uhn.fhir.rest.annotation.IdParam; 044import ca.uhn.fhir.rest.api.*; 045import ca.uhn.fhir.rest.api.server.IRestfulResponse; 046import ca.uhn.fhir.rest.server.*; 047import ca.uhn.fhir.rest.server.method.BaseMethodBinding; 048import ca.uhn.fhir.util.ReflectionUtil; 049 050/** 051 * This is the conformance provider for the jax rs servers. It requires all providers to be registered during startup because the conformance profile is generated during the postconstruct phase. 052 * 053 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare 054 */ 055@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 056public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IResourceProvider { 057 058 /** the logger */ 059 private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsConformanceProvider.class); 060 /** the server bindings */ 061 private ResourceBinding myServerBinding = new ResourceBinding(); 062 /** the resource bindings */ 063 private ConcurrentHashMap<String, ResourceBinding> myResourceNameToBinding = new ConcurrentHashMap<String, ResourceBinding>(); 064 /** the server configuration */ 065 private RestulfulServerConfiguration serverConfiguration = new RestulfulServerConfiguration(); 066 067 /** the conformance. It is created once during startup */ 068 private org.hl7.fhir.r4.model.CapabilityStatement myR4CapabilityStatement; 069 private org.hl7.fhir.dstu3.model.CapabilityStatement myDstu3CapabilityStatement; 070 private org.hl7.fhir.dstu2016may.model.Conformance myDstu2_1Conformance; 071 private org.hl7.fhir.instance.model.Conformance myDstu2Hl7OrgConformance; 072 private ca.uhn.fhir.model.dstu2.resource.Conformance myDstu2Conformance; 073 private boolean myInitialized; 074 075 /** 076 * Constructor allowing the description, servername and server to be set 077 * 078 * @param implementationDescription 079 * the implementation description. If null, "" is used 080 * @param serverName 081 * the server name. If null, "" is used 082 * @param serverVersion 083 * the server version. If null, "" is used 084 */ 085 protected AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { 086 serverConfiguration.setFhirContext(getFhirContext()); 087 serverConfiguration.setImplementationDescription(StringUtils.defaultIfEmpty(implementationDescription, "")); 088 serverConfiguration.setServerName(StringUtils.defaultIfEmpty(serverName, "")); 089 serverConfiguration.setServerVersion(StringUtils.defaultIfEmpty(serverVersion, "")); 090 } 091 092 /** 093 * Constructor allowing the description, servername and server to be set 094 * 095 * @param ctx 096 * the {@link FhirContext} instance. 097 * @param implementationDescription 098 * the implementation description. If null, "" is used 099 * @param serverName 100 * the server name. If null, "" is used 101 * @param serverVersion 102 * the server version. If null, "" is used 103 */ 104 protected AbstractJaxRsConformanceProvider(FhirContext ctx, String implementationDescription, String serverName, String serverVersion) { 105 super(ctx); 106 serverConfiguration.setFhirContext(ctx); 107 serverConfiguration.setImplementationDescription(StringUtils.defaultIfEmpty(implementationDescription, "")); 108 serverConfiguration.setServerName(StringUtils.defaultIfEmpty(serverName, "")); 109 serverConfiguration.setServerVersion(StringUtils.defaultIfEmpty(serverVersion, "")); 110 } 111 112 /** 113 * This method will set the conformance during the postconstruct phase. The method {@link AbstractJaxRsConformanceProvider#getProviders()} is used to get all the resource providers include in the 114 * conformance 115 */ 116 @PostConstruct 117 protected synchronized void setUpPostConstruct() { 118 if (myInitialized) { 119 return; 120 } 121 122 for (Entry<Class<? extends IResourceProvider>, IResourceProvider> provider : getProviders().entrySet()) { 123 addProvider(provider.getValue(), provider.getKey()); 124 } 125 List<BaseMethodBinding<?>> serverBindings = new ArrayList<BaseMethodBinding<?>>(); 126 for (ResourceBinding baseMethodBinding : myResourceNameToBinding.values()) { 127 serverBindings.addAll(baseMethodBinding.getMethodBindings()); 128 } 129 serverConfiguration.setServerBindings(serverBindings); 130 serverConfiguration.setResourceBindings(new LinkedList<ResourceBinding>(myResourceNameToBinding.values())); 131 HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(); 132 hardcodedServerAddressStrategy.setValue(getBaseForServer()); 133 serverConfiguration.setServerAddressStrategy(hardcodedServerAddressStrategy); 134 FhirVersionEnum fhirContextVersion = super.getFhirContext().getVersion().getVersion(); 135 switch (fhirContextVersion) { 136 case R4: 137 org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider r4ServerCapabilityStatementProvider = new org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider(serverConfiguration); 138 r4ServerCapabilityStatementProvider.initializeOperations(); 139 myR4CapabilityStatement = r4ServerCapabilityStatementProvider.getServerConformance(null); 140 break; 141 case DSTU3: 142 org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider dstu3ServerCapabilityStatementProvider = new org.hl7.fhir.dstu3.hapi.rest.server.ServerCapabilityStatementProvider(serverConfiguration); 143 dstu3ServerCapabilityStatementProvider.initializeOperations(); 144 myDstu3CapabilityStatement = dstu3ServerCapabilityStatementProvider.getServerConformance(null); 145 break; 146 case DSTU2_1: 147 org.hl7.fhir.dstu2016may.hapi.rest.server.ServerConformanceProvider dstu2_1ServerConformanceProvider = new org.hl7.fhir.dstu2016may.hapi.rest.server.ServerConformanceProvider(serverConfiguration); 148 dstu2_1ServerConformanceProvider.initializeOperations(); 149 myDstu2_1Conformance = dstu2_1ServerConformanceProvider.getServerConformance(null); 150 break; 151 case DSTU2_HL7ORG: 152 org.hl7.fhir.instance.conf.ServerConformanceProvider dstu2Hl7OrgServerConformanceProvider = new org.hl7.fhir.instance.conf.ServerConformanceProvider(serverConfiguration); 153 dstu2Hl7OrgServerConformanceProvider.initializeOperations(); 154 myDstu2Hl7OrgConformance = dstu2Hl7OrgServerConformanceProvider.getServerConformance(null); 155 break; 156 case DSTU2: 157 ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider dstu2ServerConformanceProvider = new ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider(serverConfiguration); 158 dstu2ServerConformanceProvider.initializeOperations(); 159 myDstu2Conformance = dstu2ServerConformanceProvider.getServerConformance(null); 160 break; 161 default: 162 throw new ConfigurationException("Unsupported Fhir version: " + fhirContextVersion); 163 } 164 165 myInitialized = true; 166 } 167 168 /** 169 * This method must return all the resource providers which need to be included in the conformance 170 * 171 * @return a map of the resource providers and their corresponding classes. This class needs to be given explicitly because retrieving the interface using {@link Object#getClass()} may not give the 172 * correct interface in a jee environment. 173 */ 174 protected abstract ConcurrentHashMap<Class<? extends IResourceProvider>, IResourceProvider> getProviders(); 175 176 /** 177 * This method will retrieve the conformance using the http OPTIONS method 178 * 179 * @return the response containing the conformance 180 */ 181 @OPTIONS 182 @Path("/metadata") 183 public Response conformanceUsingOptions() throws IOException { 184 return conformance(); 185 } 186 187 /** 188 * This method will retrieve the conformance using the http GET method 189 * 190 * @return the response containing the conformance 191 */ 192 @GET 193 @Path("/metadata") 194 public Response conformance() throws IOException { 195 setUpPostConstruct(); 196 197 Builder request = getRequest(RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA); 198 IRestfulResponse response = request.build().getResponse(); 199 response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*"); 200 201 IBaseResource conformance; 202 FhirVersionEnum fhirContextVersion = super.getFhirContext().getVersion().getVersion(); 203 switch (fhirContextVersion) { 204 case R4: 205 conformance = myR4CapabilityStatement; 206 break; 207 case DSTU3: 208 conformance = myDstu3CapabilityStatement; 209 break; 210 case DSTU2_1: 211 conformance = myDstu2_1Conformance; 212 break; 213 case DSTU2_HL7ORG: 214 conformance = myDstu2Hl7OrgConformance; 215 break; 216 case DSTU2: 217 conformance = myDstu2Conformance; 218 break; 219 default: 220 throw new ConfigurationException("Unsupported Fhir version: " + fhirContextVersion); 221 } 222 223 if (conformance != null) { 224 Set<SummaryEnum> summaryMode = Collections.emptySet(); 225 return (Response) response.streamResponseAsResource(conformance, false, summaryMode, Constants.STATUS_HTTP_200_OK, null, true, false); 226 } 227 return (Response) response.returnResponse(null, Constants.STATUS_HTTP_500_INTERNAL_ERROR, true, null, getResourceType().getSimpleName()); 228 } 229 230 /** 231 * This method will add a provider to the conformance. This method is almost an exact copy of {@link ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods } 232 * 233 * @param theProvider 234 * an instance of the provider interface 235 * @param theProviderInterface 236 * the class describing the providers interface 237 * @return the numbers of basemethodbindings added 238 * @see ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods 239 */ 240 public int addProvider(IResourceProvider theProvider, Class<? extends IResourceProvider> theProviderInterface) throws ConfigurationException { 241 int count = 0; 242 243 for (Method m : ReflectionUtil.getDeclaredMethods(theProviderInterface)) { 244 BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider); 245 if (foundMethodBinding == null) { 246 continue; 247 } 248 249 count++; 250 251 // if (foundMethodBinding instanceof ConformanceMethodBinding) { 252 // myServerConformanceMethod = foundMethodBinding; 253 // continue; 254 // } 255 256 if (!Modifier.isPublic(m.getModifiers())) { 257 throw new ConfigurationException("Method '" + m.getName() + "' is not public, FHIR RESTful methods must be public"); 258 } else { 259 if (Modifier.isStatic(m.getModifiers())) { 260 throw new ConfigurationException("Method '" + m.getName() + "' is static, FHIR RESTful methods must not be static"); 261 } else { 262 ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); 263 264 String resourceName = foundMethodBinding.getResourceName(); 265 ResourceBinding resourceBinding; 266 if (resourceName == null) { 267 resourceBinding = myServerBinding; 268 } else { 269 RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName); 270 if (myResourceNameToBinding.containsKey(definition.getName())) { 271 resourceBinding = myResourceNameToBinding.get(definition.getName()); 272 } else { 273 resourceBinding = new ResourceBinding(); 274 resourceBinding.setResourceName(resourceName); 275 myResourceNameToBinding.put(resourceName, resourceBinding); 276 } 277 } 278 279 List<Class<?>> allowableParams = foundMethodBinding.getAllowableParamAnnotations(); 280 if (allowableParams != null) { 281 for (Annotation[] nextParamAnnotations : m.getParameterAnnotations()) { 282 for (Annotation annotation : nextParamAnnotations) { 283 Package pack = annotation.annotationType().getPackage(); 284 if (pack.equals(IdParam.class.getPackage())) { 285 if (!allowableParams.contains(annotation.annotationType())) { 286 throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation); 287 } 288 } 289 } 290 } 291 } 292 293 resourceBinding.addMethod(foundMethodBinding); 294 ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); 295 } 296 } 297 } 298 299 return count; 300 } 301 302 @SuppressWarnings("unchecked") 303 @Override 304 public Class<IBaseResource> getResourceType() { 305 FhirVersionEnum fhirContextVersion = super.getFhirContext().getVersion().getVersion(); 306 switch (fhirContextVersion) { 307 case R4: 308 return Class.class.cast(org.hl7.fhir.r4.model.CapabilityStatement.class); 309 case DSTU3: 310 return Class.class.cast(org.hl7.fhir.dstu3.model.CapabilityStatement.class); 311 case DSTU2_1: 312 return Class.class.cast(org.hl7.fhir.dstu2016may.model.Conformance.class); 313 case DSTU2_HL7ORG: 314 return Class.class.cast(org.hl7.fhir.instance.model.Conformance.class); 315 case DSTU2: 316 return Class.class.cast(ca.uhn.fhir.model.dstu2.resource.Conformance.class); 317 default: 318 throw new ConfigurationException("Unsupported Fhir version: " + fhirContextVersion); 319 } 320 } 321 322}