001package ca.uhn.fhir.jaxrs.server.util; 002 003/* 004 * #%L 005 * HAPI FHIR JAX-RS Server 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 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.i18n.Msg; 024import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; 025import ca.uhn.fhir.rest.annotation.Search; 026import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 027import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; 028import ca.uhn.fhir.rest.server.method.BaseMethodBinding; 029import ca.uhn.fhir.rest.server.method.OperationMethodBinding; 030import ca.uhn.fhir.rest.server.method.SearchMethodBinding; 031import ca.uhn.fhir.util.ReflectionUtil; 032import org.apache.commons.lang3.StringUtils; 033 034import java.lang.reflect.Method; 035import java.util.LinkedHashSet; 036import java.util.List; 037import java.util.concurrent.ConcurrentHashMap; 038 039/** 040 * Class that contains the method bindings defined by a ResourceProvider 041 * 042 * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare 043 */ 044public class JaxRsMethodBindings { 045 046 /** DEFAULT_METHOD_KEY="" */ 047 public static final String DEFAULT_METHOD_KEY = ""; 048 /** Static collection of bindings mapped to a class*/ 049 private static final ConcurrentHashMap<Class<?>, JaxRsMethodBindings> classBindings = new ConcurrentHashMap<Class<?>, JaxRsMethodBindings>(); 050 /** Static collection of operationBindings mapped to a class */ 051 private ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String, BaseMethodBinding<?>>> operationBindings = 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(AbstractJaxRsProvider theProvider, Class<? extends AbstractJaxRsProvider> theProviderClass) { 059 List<Method> declaredMethodsForCurrentProvider = ReflectionUtil.getDeclaredMethods(theProviderClass); 060 declaredMethodsForCurrentProvider.addAll(ReflectionUtil.getDeclaredMethods(theProviderClass.getSuperclass())); 061 for (final Method m : declaredMethodsForCurrentProvider) { 062 final BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider); 063 if (foundMethodBinding == null) { 064 continue; 065 } 066 String bindingKey = getBindingKey(foundMethodBinding); 067 addMethodBinding(bindingKey, foundMethodBinding); 068 } 069 } 070 071 /** 072 * Get the key for the baseMethodBinding. This is: 073 * <ul> 074 * <li>the compartName for SearchMethodBindings 075 * <li>the methodName for OperationMethodBindings 076 * <li> {@link #DEFAULT_METHOD_KEY} for all other MethodBindings 077 * </ul> 078 * @param theBinding the methodbinding 079 * @return the key for the methodbinding. 080 */ 081 private String getBindingKey(final BaseMethodBinding<?> theBinding) { 082 if (theBinding instanceof OperationMethodBinding) { 083 return ((OperationMethodBinding) theBinding).getName(); 084 } else if (theBinding instanceof SearchMethodBinding) { 085 Search search = theBinding.getMethod().getAnnotation(Search.class); 086 return search.compartmentName(); 087 } else { 088 return DEFAULT_METHOD_KEY; 089 } 090 } 091 092 private void addMethodBinding(String key, BaseMethodBinding<?> binding) { 093 ConcurrentHashMap<String, BaseMethodBinding<?>> mapByOperation = getMapForOperation(binding.getRestOperationType()); 094 if (mapByOperation.containsKey(key)) { 095 throw new IllegalArgumentException(Msg.code(597) + "Multiple Search Method Bindings Found : " + mapByOperation.get(key) + " -- " + binding.getMethod()); 096 } 097 mapByOperation.put(key, binding); 098 } 099 100 /** 101 * Get the map for the given operation type. If no map exists for this operation type, create a new hashmap for this 102 * operation type and add it to the operation bindings. 103 * 104 * @param operationType the operation type. 105 * @return the map defined in the operation bindings 106 */ 107 private ConcurrentHashMap<String, BaseMethodBinding<?>> getMapForOperation(RestOperationTypeEnum operationType) { 108 ConcurrentHashMap<String, BaseMethodBinding<?>> result = operationBindings.get(operationType); 109 if(result == null) { 110 operationBindings.putIfAbsent(operationType, new ConcurrentHashMap<String, BaseMethodBinding<?>>()); 111 return getMapForOperation(operationType); 112 } else { 113 return result; 114 } 115 } 116 117 /** 118 * Get the binding 119 * 120 * @param operationType the type of operation 121 * @param theBindingKey the binding key 122 * @return the binding defined 123 * @throws NotImplementedOperationException cannot be found 124 */ 125 public BaseMethodBinding<?> getBinding(RestOperationTypeEnum operationType, String theBindingKey) { 126 String bindingKey = StringUtils.defaultIfBlank(theBindingKey, DEFAULT_METHOD_KEY); 127 ConcurrentHashMap<String, BaseMethodBinding<?>> map = getMapForOperation(operationType); 128 if(map == null || !map.containsKey(bindingKey)) { 129 throw new NotImplementedOperationException(Msg.code(598) + "Operation not implemented"); 130 } else { 131 return map.get(bindingKey); 132 } 133 } 134 135 /** 136 * 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 137 * 138 * @param theProvider the implementation class 139 * @param theProviderClass the provider class 140 * @return the methodBindings for this class 141 */ 142 public static JaxRsMethodBindings getMethodBindings(AbstractJaxRsProvider theProvider, Class<? extends AbstractJaxRsProvider> theProviderClass) { 143 if(!getClassBindings().containsKey(theProviderClass)) { 144 JaxRsMethodBindings foundBindings = new JaxRsMethodBindings(theProvider, theProviderClass); 145 getClassBindings().putIfAbsent(theProviderClass, foundBindings); 146 } 147 return getClassBindings().get(theProviderClass); 148 } 149 150 /** 151 * @return the classBindings 152 */ 153 static ConcurrentHashMap<Class<?>, JaxRsMethodBindings> getClassBindings() { 154 return classBindings; 155 } 156 157 158}