001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.ws.client;
015
016import java.lang.reflect.Method;
017import java.lang.reflect.Modifier;
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.commons.lang3.StringUtils;
025import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
026import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor;
027import org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor;
028import org.springframework.cloud.openfeign.annotation.RequestHeaderParameterProcessor;
029import org.springframework.cloud.openfeign.annotation.RequestParamParameterProcessor;
030import org.springframework.cloud.openfeign.support.SpringMvcContract;
031import org.springframework.web.bind.annotation.RequestMapping;
032
033import feign.MethodMetadata;
034import feign.Util;
035
036import static feign.Util.checkState;
037import static feign.Util.emptyToNull;
038import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
039
040public class ClientContract extends SpringMvcContract {
041
042  private ClientContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
043    super(annotatedParameterProcessors);
044  }
045
046  public static ClientContract withDefaultProcessors() {
047    return new ClientContract(
048        Arrays.asList(
049            new PartialDateParameterProcessor(),
050            new PathVariableParameterProcessor(),
051            new RequestParamParameterProcessor(),
052            new RequestHeaderParameterProcessor(),
053            new QueryMapParameterProcessor()));
054  }
055
056  public static ClientContract withProcessors(
057      AnnotatedParameterProcessor... annotatedParameterProcessors) {
058    return new ClientContract(Arrays.asList(annotatedParameterProcessors));
059  }
060
061  @Override
062  public List<MethodMetadata> parseAndValidateMetadata(final Class<?> targetType) {
063    checkState(
064        targetType.getTypeParameters().length == 0,
065        "Parameterized types unsupported: %s",
066        targetType.getSimpleName());
067    final Map<String, MethodMetadata> result = new LinkedHashMap<>();
068
069    for (final Method method : targetType.getMethods()) {
070      if (method.getDeclaringClass() == Object.class
071          || (method.getModifiers() & Modifier.STATIC) != 0
072          || Util.isDefault(method)
073          // skip default methods which related to generic inheritance
074          // also default methods are considered as "unsupported operations"
075          || method.toString().startsWith("public default")
076          // skip not annotated methods (consider as "not implemented")
077          || method.getAnnotations().length == 0) {
078        continue;
079      }
080      final MethodMetadata metadata = this.parseAndValidateMetadata(targetType, method);
081      checkState(
082          !result.containsKey(metadata.configKey()),
083          "Overrides unsupported: %s",
084          metadata.configKey());
085      result.put(metadata.configKey(), metadata);
086    }
087
088    return new ArrayList<>(result.values());
089  }
090
091  @Override
092  protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
093    RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
094    if (classAnnotation != null) {
095      // Prepend path from class annotation if specified
096      if (classAnnotation.value().length > 0) {
097        String pathValue = emptyToNull(classAnnotation.value()[0]);
098        data.template().uri(StringUtils.prependIfMissing(pathValue, "/"));
099      }
100    }
101  }
102}