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