Completed
Pull Request — master (#66)
by Filip
26:34 queued 06:50
created

createDescription(String,ResponseRepresentation)   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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