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}