Completed
Push — master ( f167e3...5f770e )
by Filip
05:24
created

createInteractionsFromMethods(ClientMethodRepresentationExtractor,List,ObjectMapper,String)   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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