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