001
002package io.vrap.rmf.base.client;
003
004import java.nio.charset.StandardCharsets;
005import java.time.ZoneOffset;
006import java.time.ZonedDateTime;
007import java.time.format.DateTimeFormatter;
008import java.util.Optional;
009
010import javax.annotation.Nullable;
011
012import io.vrap.rmf.base.client.error.BaseException;
013import io.vrap.rmf.base.client.utils.json.JsonUtils;
014
015public class ApiHttpException extends BaseException {
016
017    private final int statusCode;
018    @Nullable
019    private final String body;
020    @Nullable
021    private final ApiHttpHeaders headers;
022    @Nullable
023    private final ApiHttpResponse<byte[]> response;
024    @Nullable
025    private final ApiHttpRequest request;
026
027    private final ResponseSerializer serializer;
028
029    private final String dateAsString = DateTimeFormatter.ISO_INSTANT
030            .format(ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC));
031
032    public ApiHttpException(final int statusCode, final String body, final ApiHttpHeaders headers) {
033        this(statusCode, body, headers, null, null, null, ResponseSerializer.of());
034    }
035
036    public ApiHttpException(final int statusCode, final String body, final ApiHttpHeaders headers,
037            final ApiHttpResponse<byte[]> response) {
038        this(statusCode, body, headers, null, response, null, ResponseSerializer.of());
039    }
040
041    public ApiHttpException(final int statusCode, final String body, final ApiHttpHeaders headers, final String message,
042            final ApiHttpResponse<byte[]> response) {
043        this(statusCode, body, headers, message, response, null, ResponseSerializer.of());
044    }
045
046    public ApiHttpException(final int statusCode, final String body, final ApiHttpHeaders headers, final String message,
047            final ApiHttpResponse<byte[]> response, Throwable cause) {
048        this(statusCode, body, headers, message, response, null, ResponseSerializer.of(), cause);
049    }
050
051    public ApiHttpException(final int statusCode, final String body, final ApiHttpHeaders headers, final String message,
052            final ApiHttpResponse<byte[]> response, final ResponseSerializer serializer) {
053        this(statusCode, body, headers, message, response, null, serializer);
054    }
055
056    public ApiHttpException(final int statusCode, @Nullable final String body, @Nullable final ApiHttpHeaders headers,
057            @Nullable final String message, @Nullable final ApiHttpResponse<byte[]> response,
058            @Nullable final ApiHttpRequest request) {
059        this(statusCode, body, headers, message, response, request, ResponseSerializer.of());
060    }
061
062    public ApiHttpException(final int statusCode, @Nullable final String body, @Nullable final ApiHttpHeaders headers,
063            @Nullable final String message, @Nullable final ApiHttpResponse<byte[]> response,
064            @Nullable final ApiHttpRequest request, final ResponseSerializer serializer, Throwable cause) {
065        super(message, cause);
066        this.statusCode = statusCode;
067        this.body = body;
068        this.headers = headers;
069        this.response = response;
070        this.request = request;
071        this.serializer = serializer;
072    }
073
074    public ApiHttpException(final int statusCode, @Nullable final String body, @Nullable final ApiHttpHeaders headers,
075            @Nullable final String message, @Nullable final ApiHttpResponse<byte[]> response,
076            @Nullable final ApiHttpRequest request, final ResponseSerializer serializer) {
077        super(message);
078        this.statusCode = statusCode;
079        this.body = body;
080        this.headers = headers;
081        this.response = response;
082        this.request = request;
083        this.serializer = serializer;
084    }
085
086    public <T> T getBodyAs(final Class<T> clazz) throws SerializationException {
087        try {
088            return serializer.convertResponse(response, clazz).getBody();
089        }
090        catch (Exception e) {
091            throw new SerializationException(e.getMessage());
092        }
093    }
094
095    public int getStatusCode() {
096        return statusCode;
097    }
098
099    public String getBody() {
100        return body;
101    }
102
103    public ApiHttpHeaders getHeaders() {
104        return headers;
105    }
106
107    public ApiHttpResponse<byte[]> getResponse() {
108        return response;
109    }
110
111    public ApiHttpRequest getRequest() {
112        return request;
113    }
114
115    @Override
116    public final String getMessage() {
117        return Optional.ofNullable(super.getMessage()).map(s -> "detailMessage: " + s + "\n").orElse("") + httpSummary()
118                + responseBodyFormatted() + "http response: "
119                + Optional.ofNullable(getResponse()).map(Object::toString).orElse("<unknown>") + "\n" + "SDK: "
120                + BuildInfo.VERSION + "\n"
121                + Optional.ofNullable(request)
122                        .map(x -> "" + x.getMethod() + " " + x.getUri())
123                        .map(x -> "endpoint: " + x + "\n")
124                        .orElse("")
125                + "Java: " + System.getProperty("java.version") + "\n" + "cwd: " + System.getProperty("user.dir") + "\n"
126                + "request: " + Optional.ofNullable(request).map(Object::toString).orElse("<unknown>") + "\n"
127                + httpRequestLine() + requestBodyFormatted();
128    }
129
130    private String httpSummary() {
131        try {
132            final StringBuilder builder = new StringBuilder();
133            if (this.request != null) {
134                builder.append("summary: ");
135                final String httpMethod = Optional.of(this.request)
136                        .map(r -> r.getMethod().toString())
137                        .orElseGet(() -> this.request.getMethod().toString());
138
139                final String path = Optional.of(this.request)
140                        .map(ApiHttpRequest::getUri)
141                        .orElseGet(this.request::getUri)
142                        .toString();
143
144                final String responseCode = " with " + Optional.ofNullable(this.response)
145                        .map(ApiHttpResponse::getStatusCode)
146                        .map(Object::toString)
147                        .map(r -> "response code " + r)
148                        .orElse("an unknown status code");
149
150                final String correlationId = Optional.ofNullable(this.response)
151                        .map(ApiHttpResponse::getHeaders)
152                        .flatMap(headers -> headers.getHeaders(ApiHttpHeaders.X_CORRELATION_ID).stream().findFirst())
153                        .map(id -> " with " + ApiHttpHeaders.X_CORRELATION_ID + " `" + id + "`")
154                        .orElse("");
155
156                builder.append(httpMethod)
157                        .append(" ")
158                        .append(path)
159                        .append(" failed ")
160                        .append(responseCode)
161                        .append(correlationId)
162                        .append(" on ")
163                        .append(dateAsString)
164                        .append("\n");
165            }
166            return builder.toString();
167        }
168        catch (final Exception e) {
169            return "";
170        }
171    }
172
173    private String httpRequestLine() {
174        if (request == null) {
175            return "";
176        }
177        else {
178            return "http request: " + request.toString() + "\n";
179        }
180    }
181
182    private String responseBodyFormatted() {
183        try {
184            return Optional.ofNullable(response)
185                    .map(ApiHttpResponse::getBody)
186                    .map(b -> JsonUtils.prettyPrint(new String(b, StandardCharsets.UTF_8)))
187                    .map(s -> "http response formatted body: " + s + "\n")
188                    .orElse("");
189        }
190        catch (final Exception e) {
191            return "";
192        }
193    }
194
195    private String requestBodyFormatted() {
196        try {
197            final Optional<String> stringBodyOfHttpRequest = stringBodyOfHttpRequest();
198            final Optional<String> stringBodyOfHttpRequestIntentSupplier = Optional.ofNullable(request)
199                    .map(ApiHttpRequest::getSecuredBody);
200            return Optional
201                    .ofNullable(stringBodyOfHttpRequest.orElse(stringBodyOfHttpRequestIntentSupplier.orElse(null)))
202                    .map(JsonUtils::prettyPrint)
203                    .map(s -> "http request formatted body: " + s + "\n")
204                    .orElse("");
205        }
206        catch (final Exception e) {
207            return "";
208        }
209    }
210
211    private Optional<String> stringBodyOfHttpRequest() {
212        return Optional.ofNullable(request).map(ApiHttpRequest::getSecuredBody);
213    }
214}