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}