package ca.uhn.fhir.jaxrs.server.util;
/*
* #%L
* HAPI FHIR JAX-RS Server
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.OperationMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import ca.uhn.fhir.util.ReflectionUtil;
/**
* Class that contains the method bindings defined by a ResourceProvider
*
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
*/
public class JaxRsMethodBindings {
/** DEFAULT_METHOD_KEY="" */
public static final String DEFAULT_METHOD_KEY = "";
/** Static collection of bindings mapped to a class*/
private static final ConcurrentHashMap<Class<?>, JaxRsMethodBindings> classBindings = new ConcurrentHashMap<Class<?>, JaxRsMethodBindings>();
/** Static collection of operationBindings mapped to a class */
private ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String, BaseMethodBinding<?>>> operationBindings = new ConcurrentHashMap<RestOperationTypeEnum, ConcurrentHashMap<String,BaseMethodBinding<?>>>();
/**
* The constructor
* @param theProvider the provider which is an implementation of the theProviderClass
* @param theProviderClass the class definition contaning the operations
*/
public JaxRsMethodBindings(AbstractJaxRsProvider theProvider, Class<? extends AbstractJaxRsProvider> theProviderClass) {
for (final Method m : ReflectionUtil.getDeclaredMethods(theProviderClass)) {
final BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider);
if (foundMethodBinding == null) {
continue;
}
String bindingKey = getBindingKey(foundMethodBinding);
addMethodBinding(bindingKey, foundMethodBinding);
}
}
/**
* Get the key for the baseMethodBinding. This is:
* <ul>
* <li>the compartName for SearchMethodBindings
* <li>the methodName for OperationMethodBindings
* <li> {@link #DEFAULT_METHOD_KEY} for all other MethodBindings
* </ul>
* @param theBinding the methodbinding
* @return the key for the methodbinding.
*/
private String getBindingKey(final BaseMethodBinding<?> theBinding) {
if (theBinding instanceof OperationMethodBinding) {
return ((OperationMethodBinding) theBinding).getName();
} else if (theBinding instanceof SearchMethodBinding) {
Search search = theBinding.getMethod().getAnnotation(Search.class);
return search.compartmentName();
} else {
return DEFAULT_METHOD_KEY;
}
}
private void addMethodBinding(String key, BaseMethodBinding<?> binding) {
ConcurrentHashMap<String, BaseMethodBinding<?>> mapByOperation = getMapForOperation(binding.getRestOperationType());
if (mapByOperation.containsKey(key)) {
throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + mapByOperation.get(key) + " -- " + binding.getMethod());
}
mapByOperation.put(key, binding);
}
/**
* Get the map for the given operation type. If no map exists for this operation type, create a new hashmap for this
* operation type and add it to the operation bindings.
*
* @param operationType the operation type.
* @return the map defined in the operation bindings
*/
private ConcurrentHashMap<String, BaseMethodBinding<?>> getMapForOperation(RestOperationTypeEnum operationType) {
ConcurrentHashMap<String, BaseMethodBinding<?>> result = operationBindings.get(operationType);
if(result == null) {
operationBindings.putIfAbsent(operationType, new ConcurrentHashMap<String, BaseMethodBinding<?>>());
return getMapForOperation(operationType);
} else {
return result;
}
}
/**
* Get the binding
*
* @param operationType the type of operation
* @param theBindingKey the binding key
* @return the binding defined
* @throws NotImplementedOperationException cannot be found
*/
public BaseMethodBinding<?> getBinding(RestOperationTypeEnum operationType, String theBindingKey) {
String bindingKey = StringUtils.defaultIfBlank(theBindingKey, DEFAULT_METHOD_KEY);
ConcurrentHashMap<String, BaseMethodBinding<?>> map = operationBindings.get(operationType);
if(map == null || !map.containsKey(bindingKey)) {
throw new NotImplementedOperationException("Operation not implemented");
} else {
return map.get(bindingKey);
}
}
/**
* 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
*
* @param theProvider the implementation class
* @param theProviderClass the provider class
* @return the methodBindings for this class
*/
public static JaxRsMethodBindings getMethodBindings(AbstractJaxRsProvider theProvider, Class<? extends AbstractJaxRsProvider> theProviderClass) {
if(!getClassBindings().containsKey(theProviderClass)) {
JaxRsMethodBindings foundBindings = new JaxRsMethodBindings(theProvider, theProviderClass);
getClassBindings().putIfAbsent(theProviderClass, foundBindings);
}
return getClassBindings().get(theProviderClass);
}
/**
* @return the classBindings
*/
static ConcurrentHashMap<Class<?>, JaxRsMethodBindings> getClassBindings() {
return classBindings;
}
}