001package ca.uhn.fhir.jaxrs.server; 002 003/* 004 * #%L 005 * HAPI FHIR JAX-RS Server 006 * %% 007 * Copyright (C) 2014 - 2016 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.ArrayList; 028import java.util.Collections; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Map.Entry; 032import java.util.Set; 033import java.util.concurrent.ConcurrentHashMap; 034 035import javax.annotation.PostConstruct; 036import javax.ws.rs.GET; 037import javax.ws.rs.OPTIONS; 038import javax.ws.rs.Path; 039import javax.ws.rs.Produces; 040import javax.ws.rs.core.MediaType; 041import javax.ws.rs.core.Response; 042 043import org.apache.commons.lang3.StringUtils; 044import org.hl7.fhir.dstu3.hapi.rest.server.ServerConformanceProvider; 045import org.hl7.fhir.dstu3.model.Conformance; 046import org.hl7.fhir.instance.model.api.IBaseResource; 047import org.slf4j.LoggerFactory; 048 049import ca.uhn.fhir.context.ConfigurationException; 050import ca.uhn.fhir.context.FhirContext; 051import ca.uhn.fhir.context.FhirVersionEnum; 052import ca.uhn.fhir.context.RuntimeResourceDefinition; 053import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; 054import ca.uhn.fhir.rest.annotation.IdParam; 055import ca.uhn.fhir.rest.api.RequestTypeEnum; 056import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 057import ca.uhn.fhir.rest.api.SummaryEnum; 058import ca.uhn.fhir.rest.method.BaseMethodBinding; 059import ca.uhn.fhir.rest.method.ParseAction; 060import ca.uhn.fhir.rest.server.Constants; 061import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; 062import ca.uhn.fhir.rest.server.IResourceProvider; 063import ca.uhn.fhir.rest.server.IRestfulResponse; 064import ca.uhn.fhir.rest.server.ResourceBinding; 065import ca.uhn.fhir.rest.server.RestulfulServerConfiguration; 066import ca.uhn.fhir.util.ReflectionUtil; 067 068/** 069 * 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. 070 * 071 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare 072 */ 073@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) 074public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IResourceProvider { 075 076 /** the logger */ 077 private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsConformanceProvider.class); 078 /** the server bindings */ 079 private ResourceBinding myServerBinding = new ResourceBinding(); 080 /** the resource bindings */ 081 private ConcurrentHashMap<String, ResourceBinding> myResourceNameToBinding = new ConcurrentHashMap<String, ResourceBinding>(); 082 /** the server configuration */ 083 private RestulfulServerConfiguration serverConfiguration = new RestulfulServerConfiguration(); 084 085 /** the conformance. It is created once during startup */ 086 private Conformance myDstu3Conformance; 087 private ca.uhn.fhir.model.dstu2.resource.Conformance myDstu2Conformance; 088 089 /** 090 * Constructor allowing the description, servername and server to be set 091 * 092 * @param implementationDescription 093 * the implementation description. If null, "" is used 094 * @param serverName 095 * the server name. If null, "" is used 096 * @param serverVersion 097 * the server version. If null, "" is used 098 */ 099 protected AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { 100 serverConfiguration.setFhirContext(getFhirContext()); 101 serverConfiguration.setImplementationDescription(StringUtils.defaultIfEmpty(implementationDescription, "")); 102 serverConfiguration.setServerName(StringUtils.defaultIfEmpty(serverName, "")); 103 serverConfiguration.setServerVersion(StringUtils.defaultIfEmpty(serverVersion, "")); 104 } 105 106 /** 107 * Constructor allowing the description, servername and server to be set 108 * 109 * @param ctx 110 * the {@link FhirContext} instance. 111 * @param implementationDescription 112 * the implementation description. If null, "" is used 113 * @param serverName 114 * the server name. If null, "" is used 115 * @param serverVersion 116 * the server version. If null, "" is used 117 */ 118 protected AbstractJaxRsConformanceProvider(FhirContext ctx, String implementationDescription, String serverName, String serverVersion) { 119 super(ctx); 120 serverConfiguration.setFhirContext(ctx); 121 serverConfiguration.setImplementationDescription(StringUtils.defaultIfEmpty(implementationDescription, "")); 122 serverConfiguration.setServerName(StringUtils.defaultIfEmpty(serverName, "")); 123 serverConfiguration.setServerVersion(StringUtils.defaultIfEmpty(serverVersion, "")); 124 } 125 126 /** 127 * 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 128 * conformance 129 */ 130 @PostConstruct 131 protected void setUpPostConstruct() { 132 for (Entry<Class<? extends IResourceProvider>, IResourceProvider> provider : getProviders().entrySet()) { 133 addProvider(provider.getValue(), provider.getKey()); 134 } 135 List<BaseMethodBinding<?>> serverBindings = new ArrayList<BaseMethodBinding<?>>(); 136 for (ResourceBinding baseMethodBinding : myResourceNameToBinding.values()) { 137 serverBindings.addAll(baseMethodBinding.getMethodBindings()); 138 } 139 serverConfiguration.setServerBindings(serverBindings); 140 serverConfiguration.setResourceBindings(new LinkedList<ResourceBinding>(myResourceNameToBinding.values())); 141 HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(); 142 hardcodedServerAddressStrategy.setValue(getBaseForServer()); 143 serverConfiguration.setServerAddressStrategy(hardcodedServerAddressStrategy); 144 if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { 145 ServerConformanceProvider serverConformanceProvider = new ServerConformanceProvider(serverConfiguration); 146 serverConformanceProvider.initializeOperations(); 147 myDstu3Conformance = serverConformanceProvider.getServerConformance(null); 148 } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { 149 ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider serverConformanceProvider = new ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider(serverConfiguration); 150 serverConformanceProvider.initializeOperations(); 151 myDstu2Conformance = serverConformanceProvider.getServerConformance(null); 152 } 153 } 154 155 /** 156 * This method must return all the resource providers which need to be included in the conformance 157 * 158 * @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 159 * correct interface in a jee environment. 160 */ 161 protected abstract ConcurrentHashMap<Class<? extends IResourceProvider>, IResourceProvider> getProviders(); 162 163 /** 164 * This method will retrieve the conformance using the http OPTIONS method 165 * 166 * @return the response containing the conformance 167 */ 168 @OPTIONS 169 @Path("/metadata") 170 public Response conformanceUsingOptions() throws IOException { 171 return conformance(); 172 } 173 174 /** 175 * This method will retrieve the conformance using the http GET method 176 * 177 * @return the response containing the conformance 178 */ 179 @GET 180 @Path("/metadata") 181 public Response conformance() throws IOException { 182 Builder request = getRequest(RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA); 183 IRestfulResponse response = request.build().getResponse(); 184 response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*"); 185 186 IBaseResource conformance = null; 187 if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { 188 conformance = myDstu3Conformance; 189// return (Response) response.returnResponse(ParseAction.create(myDstu3Conformance), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); 190 } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { 191 conformance = myDstu2Conformance; 192// return (Response) response.returnResponse(ParseAction.create(myDstu2Conformance), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); 193 } 194 195 if (conformance != null) { 196 Set<SummaryEnum> summaryMode = Collections.emptySet(); 197 return (Response) response.streamResponseAsResource(conformance, false, summaryMode, Constants.STATUS_HTTP_200_OK, null, true, false); 198 } 199 return (Response) response.returnResponse(null, Constants.STATUS_HTTP_500_INTERNAL_ERROR, true, null, getResourceType().getSimpleName()); 200 } 201 202 /** 203 * 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 } 204 * 205 * @param theProvider 206 * an instance of the provider interface 207 * @param theProviderInterface 208 * the class describing the providers interface 209 * @return the numbers of basemethodbindings added 210 * @see ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods 211 */ 212 public int addProvider(IResourceProvider theProvider, Class<? extends IResourceProvider> theProviderInterface) throws ConfigurationException { 213 int count = 0; 214 215 for (Method m : ReflectionUtil.getDeclaredMethods(theProviderInterface)) { 216 BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider); 217 if (foundMethodBinding == null) { 218 continue; 219 } 220 221 count++; 222 223 // if (foundMethodBinding instanceof ConformanceMethodBinding) { 224 // myServerConformanceMethod = foundMethodBinding; 225 // continue; 226 // } 227 228 if (!Modifier.isPublic(m.getModifiers())) { 229 throw new ConfigurationException("Method '" + m.getName() + "' is not public, FHIR RESTful methods must be public"); 230 } else { 231 if (Modifier.isStatic(m.getModifiers())) { 232 throw new ConfigurationException("Method '" + m.getName() + "' is static, FHIR RESTful methods must not be static"); 233 } else { 234 ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); 235 236 String resourceName = foundMethodBinding.getResourceName(); 237 ResourceBinding resourceBinding; 238 if (resourceName == null) { 239 resourceBinding = myServerBinding; 240 } else { 241 RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName); 242 if (myResourceNameToBinding.containsKey(definition.getName())) { 243 resourceBinding = myResourceNameToBinding.get(definition.getName()); 244 } else { 245 resourceBinding = new ResourceBinding(); 246 resourceBinding.setResourceName(resourceName); 247 myResourceNameToBinding.put(resourceName, resourceBinding); 248 } 249 } 250 251 List<Class<?>> allowableParams = foundMethodBinding.getAllowableParamAnnotations(); 252 if (allowableParams != null) { 253 for (Annotation[] nextParamAnnotations : m.getParameterAnnotations()) { 254 for (Annotation annotation : nextParamAnnotations) { 255 Package pack = annotation.annotationType().getPackage(); 256 if (pack.equals(IdParam.class.getPackage())) { 257 if (!allowableParams.contains(annotation.annotationType())) { 258 throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation); 259 } 260 } 261 } 262 } 263 } 264 265 resourceBinding.addMethod(foundMethodBinding); 266 ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); 267 } 268 } 269 } 270 271 return count; 272 } 273 274 @SuppressWarnings("unchecked") 275 @Override 276 public Class<IBaseResource> getResourceType() { 277 if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU3)) { 278 return Class.class.cast(Conformance.class); 279 } else if (super.getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) { 280 return Class.class.cast(ca.uhn.fhir.model.dstu2.resource.Conformance.class); 281 } 282 return null; 283 } 284 285}