createFromFeignClient(Class,String,ObjectMapper)   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 13
dl 0
loc 15
rs 9.75
c 1
b 0
f 0
1
package com.hltech.pact.gen.domain.pact;
2
3
import com.fasterxml.jackson.databind.JsonNode;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
6
import com.hltech.pact.gen.PactGenerationException;
7
import com.hltech.pact.gen.domain.client.ClientMethodRepresentationExtractor;
8
import com.hltech.pact.gen.domain.client.annotation.HandlersFactory;
9
import com.hltech.pact.gen.domain.client.annotation.MappingHandlerFactory;
10
import com.hltech.pact.gen.domain.client.annotation.handlers.AnnotatedMethodHandler;
11
import com.hltech.pact.gen.domain.client.feign.ExcludeFeignInteraction;
12
import com.hltech.pact.gen.domain.client.feign.FeignMethodRepresentationExtractor;
13
import com.hltech.pact.gen.domain.client.model.ClientMethodRepresentation;
14
import com.hltech.pact.gen.domain.client.model.Param;
15
import com.hltech.pact.gen.domain.client.model.RequestRepresentation;
16
import com.hltech.pact.gen.domain.client.model.ResponseRepresentation;
17
import com.hltech.pact.gen.domain.pact.model.Interaction;
18
import com.hltech.pact.gen.domain.pact.model.InteractionRequest;
19
import com.hltech.pact.gen.domain.pact.model.InteractionResponse;
20
import com.hltech.pact.gen.domain.pact.model.Metadata;
21
import com.hltech.pact.gen.domain.pact.model.Pact;
22
import lombok.extern.slf4j.Slf4j;
23
import org.apache.commons.lang3.StringUtils;
24
import org.springframework.cloud.openfeign.FeignClient;
25
import uk.co.jemos.podam.api.PodamFactory;
26
import uk.co.jemos.podam.api.PodamFactoryImpl;
27
28
import java.lang.reflect.Method;
29
import java.math.BigDecimal;
30
import java.math.BigInteger;
31
import java.util.Arrays;
32
import java.util.Collection;
33
import java.util.HashMap;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.stream.Collectors;
37
38
@Slf4j
39
public class PactFactory {
40
41
    private static final PodamFactory podamFactory;
42
    private static final Collection<AnnotatedMethodHandler> annotatedMethodHandlers;
43
44
    static {
45
        podamFactory = new PodamFactoryImpl();
46
        podamFactory.getStrategy().addOrReplaceTypeManufacturer(String.class, new EnumStringManufacturer());
47
        podamFactory.getStrategy().addOrReplaceTypeManufacturer(BigInteger.class, new BigIntegerManufacturer());
48
        podamFactory.getStrategy().addOrReplaceTypeManufacturer(BigDecimal.class, new BigDecimalManufacturer());
49
        podamFactory.getStrategy().setDefaultNumberOfCollectionElements(1);
50
    }
51
52
    static {
53
        annotatedMethodHandlers = new MappingHandlerFactory(new HandlersFactory()).createAll();
54
    }
55
56
    public Pact createFromFeignClient(Class<?> feignClient, String consumerName, ObjectMapper objectMapper) {
57
        ClientMethodRepresentationExtractor methodExtractor =
58
            new FeignMethodRepresentationExtractor(annotatedMethodHandlers);
59
60
        List<Method> validFeignMethods = Arrays.stream(feignClient.getMethods())
61
            .filter(method -> !method.isAnnotationPresent(ExcludeFeignInteraction.class))
62
            .collect(Collectors.toList());
63
64
        return Pact.builder()
65
            .provider(new Service(extractProviderName(feignClient)))
66
            .consumer(new Service(consumerName))
67
            .interactions(createInteractionsFromMethods(
68
                methodExtractor, validFeignMethods, objectMapper, extractPathPrefix(feignClient)))
69
            .metadata(new Metadata("1.0.0"))
70
            .build();
71
    }
72
73
    private String extractPathPrefix(Class<?> feignClient) {
74
        FeignClient feignClientAnnotation = feignClient.getAnnotation(FeignClient.class);
75
        return feignClientAnnotation.path();
76
    }
77
78
    private String extractProviderName(Class<?> feignClient) {
79
        FeignClient feignClientAnnotation = feignClient.getAnnotation(FeignClient.class);
80
        return feignClientAnnotation.value().isEmpty() ? feignClientAnnotation.name() : feignClientAnnotation.value();
81
    }
82
83
    private static List<Interaction> createInteractionsFromMethods(
84
        ClientMethodRepresentationExtractor extractor,
85
        List<Method> clientMethods,
86
        ObjectMapper objectMapper,
87
        String pathPrefix) {
88
89
        return clientMethods.stream()
90
            .filter(method -> annotatedMethodHandlers.stream()
91
                .anyMatch(handler -> handler.isSupported(method)))
92
            .flatMap(method -> createInteractionsFromMethod(extractor, method, objectMapper, pathPrefix).stream())
93
            .collect(Collectors.toList());
94
    }
95
96
    private static List<Interaction> createInteractionsFromMethod(
97
        ClientMethodRepresentationExtractor extractor,
98
        Method clientMethod,
99
        ObjectMapper objectMapper,
100
        String pathPrefix) {
101
102
        ClientMethodRepresentation methodRepresentation = extractor.extractClientMethodRepresentation(clientMethod);
103
104
        PojoValidator.validateAll(PojoExtractor.extractPojoTypes(methodRepresentation));
105
106
        return methodRepresentation.getResponseRepresentationList().stream()
107
            .map(interactionResponseRepresentation -> Interaction.builder()
108
                .description(createDescription(clientMethod.getName(), interactionResponseRepresentation))
109
                .request(
110
                    createInteractionRequest(methodRepresentation.getRequestRepresentation(), objectMapper, pathPrefix))
111
                .response(createInteractionResponse(interactionResponseRepresentation, objectMapper))
112
                .build())
113
            .collect(Collectors.toList());
114
    }
115
116
    private static String createDescription(String feignMethodName, ResponseRepresentation response) {
117
        if (response.getDescription().isEmpty()) {
118
            return String.format("%s request; %s response", feignMethodName, response.getStatus());
119
        }
120
121
        return response.getDescription();
122
    }
123
124
    private static InteractionRequest createInteractionRequest(
125
        RequestRepresentation requestRepresentation, ObjectMapper objectMapper, String pathPrefix) {
126
127
        return InteractionRequest.builder()
128
            .method(requestRepresentation.getHttpMethod().name())
129
            .path(parsePath(pathPrefix, requestRepresentation.getPath(), requestRepresentation.getPathParameters()))
130
            .headers(mapHeaders(requestRepresentation.getHeaders()))
131
            .query(parseParametersToQuery(requestRepresentation.getRequestParameters()))
132
            .body(BodySerializer.serializeBody(requestRepresentation.getBody(), objectMapper, podamFactory))
133
            .build();
134
    }
135
136
    private static String parsePath(String pathPrefix, String path, List<Param> pathParameters) {
137
        String resultPath = path;
138
        for (Param param : pathParameters) {
139
            Object paramValue = getParamValue(param);
140
141
            resultPath = resultPath.replace("{" + param.getName() + "}", String.valueOf(paramValue));
142
        }
143
        return prependPrefix(pathPrefix, resultPath);
144
    }
145
146
    private static String prependPrefix(String pathPrefix, String path) {
147
        if (pathPrefix.length() == 0) {
148
            return path;
149
        }
150
151
        StringBuilder builder = new StringBuilder(pathPrefix);
152
153
        if (pathPrefix.charAt(pathPrefix.length() - 1) == '/') {
154
            builder.deleteCharAt(pathPrefix.length() - 1);
155
        }
156
157
        if (path.charAt(0) != '/') {
158
            builder.append('/');
159
        }
160
161
        return builder.append(path).toString();
162
    }
163
164
    private static Object getParamValue(Param param) {
165
        if (param.getDefaultValue() != null) {
166
            return param.getDefaultValue();
167
        }
168
169
        if (param.getGenericArgumentType() != null) {
170
            return manufacturePojo(param.getGenericArgumentType());
171
        }
172
173
        return manufacturePojo(param.getType());
174
    }
175
176
    private static Object manufacturePojo(Class<?> type) {
177
        Object manufacturedPojo = podamFactory.manufacturePojo(type);
178
179
        if (manufacturedPojo == null) {
180
            throw new PactGenerationException("Podam manufacturing failed");
181
        }
182
183
        return manufacturedPojo;
184
    }
185
186
    private static String parseParametersToQuery(List<Param> requestParameters) {
187
        StringBuilder queryBuilder = new StringBuilder();
188
189
        requestParameters
190
            .forEach(param -> queryBuilder
191
                .append(param.getName())
192
                .append("=")
193
                .append(getParamValue(param))
194
                .append("&"));
195
196
        if (queryBuilder.length() != 0) {
197
            queryBuilder.deleteCharAt(queryBuilder.length() - 1);
198
        }
199
200
        return queryBuilder.toString();
201
    }
202
203
    private static InteractionResponse createInteractionResponse(
204
        ResponseRepresentation responseRepresentation,
205
        ObjectMapper objectMapper) {
206
207
        return InteractionResponse.builder()
208
            .status(Integer.toString(responseRepresentation.getStatus().value()))
209
            .headers(mapHeaders(responseRepresentation.getHeaders()))
210
            .body(buildBody(responseRepresentation, objectMapper))
211
            .build();
212
    }
213
214
    private static Map<String, String> mapHeaders(List<Param> headers) {
215
        Map<String, List<String>> mappedHeadersWithDuplicates = mapHeadersWithDuplicates(headers);
216
217
        Map<String, String> resultingHeaders = new HashMap<>();
218
        for (String key: mappedHeadersWithDuplicates.keySet()) {
219
            if (mappedHeadersWithDuplicates.get(key).size() > 1) {
220
                log.warn("More than one value for header: {}", key);
221
            }
222
223
            resultingHeaders.put(key, mappedHeadersWithDuplicates.get(key).get(0));
224
        }
225
226
        return resultingHeaders;
227
    }
228
229
    private static Map<String, List<String>> mapHeadersWithDuplicates(List<Param> headers) {
230
        return headers.stream()
231
            .collect(Collectors.groupingBy(
232
                Param::getName,
233
                Collectors.mapping(param -> String.valueOf(getParamValue(param)), Collectors.toList())));
234
    }
235
236
    private static JsonNode buildBody(ResponseRepresentation responseRepresentation, ObjectMapper objectMapper) {
237
        return responseRepresentation.isEmptyBodyExpected()
238
            ? JsonNodeFactory.instance.textNode(StringUtils.EMPTY)
239
            : BodySerializer.serializeBody(responseRepresentation.getBody(), objectMapper, podamFactory);
240
    }
241
}
242