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}