001package ca.uhn.fhir.rest.server.method; 002 003/* 004 * #%L 005 * HAPI FHIR - Server Framework 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 ca.uhn.fhir.context.ConfigurationException; 024import ca.uhn.fhir.context.FhirContext; 025import ca.uhn.fhir.interceptor.api.HookParams; 026import ca.uhn.fhir.interceptor.api.Pointcut; 027import ca.uhn.fhir.model.valueset.BundleTypeEnum; 028import ca.uhn.fhir.rest.annotation.Metadata; 029import ca.uhn.fhir.rest.api.CacheControlDirective; 030import ca.uhn.fhir.rest.api.Constants; 031import ca.uhn.fhir.rest.api.RequestTypeEnum; 032import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 033import ca.uhn.fhir.rest.api.server.IBundleProvider; 034import ca.uhn.fhir.rest.api.server.IRestfulServer; 035import ca.uhn.fhir.rest.api.server.RequestDetails; 036import ca.uhn.fhir.rest.server.SimpleBundleProvider; 037import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 038import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; 039import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; 040import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 041import org.hl7.fhir.instance.model.api.IBaseConformance; 042import org.hl7.fhir.instance.model.api.IBaseResource; 043 044import javax.annotation.Nonnull; 045import java.lang.reflect.Method; 046import java.util.concurrent.atomic.AtomicLong; 047import java.util.concurrent.atomic.AtomicReference; 048 049public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding { 050 /* 051 * Note: This caching mechanism should probably be configurable and maybe 052 * even applicable to other bindings. It's particularly important for this 053 * operation though, so a one-off is fine for now 054 */ 055 private final AtomicReference<IBaseResource> myCachedResponse = new AtomicReference<>(); 056 private final AtomicLong myCachedResponseExpires = new AtomicLong(0L); 057 private long myCacheMillis = 60 * 1000; 058 059 ConformanceMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { 060 super(theMethod.getReturnType(), theMethod, theContext, theProvider); 061 062 MethodReturnTypeEnum methodReturnType = getMethodReturnType(); 063 Class<?> genericReturnType = (Class<?>) theMethod.getGenericReturnType(); 064 if (methodReturnType != MethodReturnTypeEnum.RESOURCE || !IBaseConformance.class.isAssignableFrom(genericReturnType)) { 065 throw new ConfigurationException("Conformance resource provider method '" + theMethod.getName() + "' should return a Conformance resource class, returns: " + theMethod.getReturnType()); 066 } 067 068 Metadata metadata = theMethod.getAnnotation(Metadata.class); 069 if (metadata != null) { 070 setCacheMillis(metadata.cacheMillis()); 071 } 072 073 } 074 075 /** 076 * Returns the number of milliseconds to cache the generated CapabilityStatement for. Default is one minute, and can be 077 * set to 0 to never cache. 078 * 079 * @see #setCacheMillis(long) 080 * @see Metadata#cacheMillis() 081 * @since 4.1.0 082 */ 083 private long getCacheMillis() { 084 return myCacheMillis; 085 } 086 087 /** 088 * Returns the number of milliseconds to cache the generated CapabilityStatement for. Default is one minute, and can be 089 * set to 0 to never cache. 090 * 091 * @see #getCacheMillis() 092 * @see Metadata#cacheMillis() 093 * @since 4.1.0 094 */ 095 private void setCacheMillis(long theCacheMillis) { 096 myCacheMillis = theCacheMillis; 097 } 098 099 @Override 100 public ReturnTypeEnum getReturnType() { 101 return ReturnTypeEnum.RESOURCE; 102 } 103 104 @Override 105 public IBundleProvider invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { 106 IBaseResource conf; 107 108 CacheControlDirective cacheControlDirective = new CacheControlDirective().parse(theRequest.getHeaders(Constants.HEADER_CACHE_CONTROL)); 109 110 if (cacheControlDirective.isNoCache()) 111 conf = null; 112 else { 113 conf = myCachedResponse.get(); 114 if ("true".equals(System.getProperty("test"))) { 115 conf = null; 116 } 117 if (conf != null) { 118 long expires = myCachedResponseExpires.get(); 119 if (expires < System.currentTimeMillis()) { 120 conf = null; 121 } 122 } 123 } 124 if (conf != null) { 125 // Handle server action interceptors 126 RestOperationTypeEnum operationType = getRestOperationType(theRequest); 127 if (operationType != null) { 128 IServerInterceptor.ActionRequestDetails details = new IServerInterceptor.ActionRequestDetails(theRequest); 129 populateActionRequestDetailsForInterceptor(theRequest, details, theMethodParams); 130 HookParams preHandledParams = new HookParams(); 131 preHandledParams.add(RestOperationTypeEnum.class, theRequest.getRestOperationType()); 132 preHandledParams.add(RequestDetails.class, theRequest); 133 preHandledParams.addIfMatchesType(ServletRequestDetails.class, theRequest); 134 preHandledParams.add(IServerInterceptor.ActionRequestDetails.class, details); 135 if (theRequest.getInterceptorBroadcaster() != null) { 136 theRequest 137 .getInterceptorBroadcaster() 138 .callHooks(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, preHandledParams); 139 } 140 } 141 } 142 143 if (conf == null) { 144 conf = (IBaseResource) invokeServerMethod(theServer, theRequest, theMethodParams); 145 if (myCacheMillis > 0) { 146 myCachedResponse.set(conf); 147 myCachedResponseExpires.set(System.currentTimeMillis() + getCacheMillis()); 148 } 149 } 150 151 return new SimpleBundleProvider(conf); 152 } 153 154 @Override 155 public boolean incomingServerRequestMatchesMethod(RequestDetails theRequest) { 156 if (theRequest.getRequestType() == RequestTypeEnum.OPTIONS) { 157 if (theRequest.getOperation() == null && theRequest.getResourceName() == null) { 158 return true; 159 } 160 } 161 162 if (theRequest.getResourceName() != null) { 163 return false; 164 } 165 166 if ("metadata".equals(theRequest.getOperation())) { 167 if (theRequest.getRequestType() == RequestTypeEnum.GET) { 168 return true; 169 } 170 throw new MethodNotAllowedException("/metadata request must use HTTP GET", RequestTypeEnum.GET); 171 } 172 173 return false; 174 } 175 176 @Nonnull 177 @Override 178 public RestOperationTypeEnum getRestOperationType() { 179 return RestOperationTypeEnum.METADATA; 180 } 181 182 @Override 183 protected BundleTypeEnum getResponseBundleType() { 184 return null; 185 } 186 187}