001/* 002 * #%L 003 * HAPI FHIR JAX-RS Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jaxrs.server.util; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; 024import ca.uhn.fhir.rest.annotation.Search; 025import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 026import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; 027import ca.uhn.fhir.rest.server.method.BaseMethodBinding; 028import ca.uhn.fhir.rest.server.method.OperationMethodBinding; 029import ca.uhn.fhir.rest.server.method.SearchMethodBinding; 030import ca.uhn.fhir.util.ReflectionUtil; 031import org.apache.commons.lang3.StringUtils; 032 033import java.lang.reflect.Method; 034import java.util.List; 035import java.util.concurrent.ConcurrentHashMap; 036 037/** 038 * Class that contains the method bindings defined by a ResourceProvider 039 * 040 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare 041 */ 042public class JaxRsMethodBindings { 043 044 /** DEFAULT_METHOD_KEY="" */ 045 public static final String DEFAULT_METHOD_KEY = ""; 046 /** Static collection of bindings mapped to a class*/ 047 private static final ConcurrentHashMap<Class<?>, JaxRsMethodBindings> classBindings = 048 new ConcurrentHashMap<Class<?>, JaxRsMethodBindings>(); 049 /** Static collection of operationBindings mapped to a class */ 050 private ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String, BaseMethodBinding>> operationBindings = 051 new ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String, BaseMethodBinding>>(); 052 053 /** 054 * The constructor 055 * @param theProvider the provider which is an implementation of the theProviderClass 056 * @param theProviderClass the class definition contaning the operations 057 */ 058 public JaxRsMethodBindings( 059 AbstractJaxRsProvider theProvider, Class<? extends AbstractJaxRsProvider> theProviderClass) { 060 List<Method> declaredMethodsForCurrentProvider = ReflectionUtil.getDeclaredMethods(theProviderClass); 061 declaredMethodsForCurrentProvider.addAll(ReflectionUtil.getDeclaredMethods(theProviderClass.getSuperclass())); 062 for (final Method m : declaredMethodsForCurrentProvider) { 063 final BaseMethodBinding foundMethodBinding = 064 BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider); 065 if (foundMethodBinding == null) { 066 continue; 067 } 068 String bindingKey = getBindingKey(foundMethodBinding); 069 addMethodBinding(bindingKey, foundMethodBinding); 070 } 071 } 072 073 /** 074 * Get the key for the baseMethodBinding. This is: 075 * <ul> 076 * <li>the compartName for SearchMethodBindings 077 * <li>the methodName for OperationMethodBindings 078 * <li> {@link #DEFAULT_METHOD_KEY} for all other MethodBindings 079 * </ul> 080 * @param theBinding the methodbinding 081 * @return the key for the methodbinding. 082 */ 083 private String getBindingKey(final BaseMethodBinding theBinding) { 084 if (theBinding instanceof OperationMethodBinding) { 085 return ((OperationMethodBinding) theBinding).getName(); 086 } else if (theBinding instanceof SearchMethodBinding) { 087 Search search = theBinding.getMethod().getAnnotation(Search.class); 088 return search.compartmentName(); 089 } else { 090 return DEFAULT_METHOD_KEY; 091 } 092 } 093 094 private void addMethodBinding(String key, BaseMethodBinding binding) { 095 ConcurrentHashMap<String, BaseMethodBinding> mapByOperation = 096 getMapForOperation(binding.getRestOperationType()); 097 if (mapByOperation.containsKey(key)) { 098 throw new IllegalArgumentException(Msg.code(597) + "Multiple Search Method Bindings Found : " 099 + mapByOperation.get(key) + " -- " + binding.getMethod()); 100 } 101 mapByOperation.put(key, binding); 102 } 103 104 /** 105 * Get the map for the given operation type. If no map exists for this operation type, create a new hashmap for this 106 * operation type and add it to the operation bindings. 107 * 108 * @param operationType the operation type. 109 * @return the map defined in the operation bindings 110 */ 111 private ConcurrentHashMap<String, BaseMethodBinding> getMapForOperation(RestOperationTypeEnum operationType) { 112 ConcurrentHashMap<String, BaseMethodBinding> result = operationBindings.get(operationType); 113 if (result == null) { 114 operationBindings.putIfAbsent(operationType, new ConcurrentHashMap<String, BaseMethodBinding>()); 115 return getMapForOperation(operationType); 116 } else { 117 return result; 118 } 119 } 120 121 /** 122 * Get the binding 123 * 124 * @param operationType the type of operation 125 * @param theBindingKey the binding key 126 * @return the binding defined 127 * @throws NotImplementedOperationException cannot be found 128 */ 129 public BaseMethodBinding getBinding(RestOperationTypeEnum operationType, String theBindingKey) { 130 String bindingKey = StringUtils.defaultIfBlank(theBindingKey, DEFAULT_METHOD_KEY); 131 ConcurrentHashMap<String, BaseMethodBinding> map = getMapForOperation(operationType); 132 if (map == null || !map.containsKey(bindingKey)) { 133 throw new NotImplementedOperationException(Msg.code(598) + "Operation not implemented"); 134 } else { 135 return map.get(bindingKey); 136 } 137 } 138 139 /** 140 * Get the method bindings for the given class. If this class is not yet contained in the classBindings, they will be added for this class 141 * 142 * @param theProvider the implementation class 143 * @param theProviderClass the provider class 144 * @return the methodBindings for this class 145 */ 146 public static JaxRsMethodBindings getMethodBindings( 147 AbstractJaxRsProvider theProvider, Class<? extends AbstractJaxRsProvider> theProviderClass) { 148 if (!getClassBindings().containsKey(theProviderClass)) { 149 JaxRsMethodBindings foundBindings = new JaxRsMethodBindings(theProvider, theProviderClass); 150 getClassBindings().putIfAbsent(theProviderClass, foundBindings); 151 } 152 return getClassBindings().get(theProviderClass); 153 } 154 155 /** 156 * @return the classBindings 157 */ 158 static ConcurrentHashMap<Class<?>, JaxRsMethodBindings> getClassBindings() { 159 return classBindings; 160 } 161}