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}