View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.spi.connector.transport.http.RFC9457;
20  
21  import java.io.IOException;
22  
23  /**
24   * A reporter for RFC 9457 messages.
25   * RFC 9457 is a standard for reporting problems in HTTP responses as a JSON object.
26   * There are members specified in the RFC but none of those appear to be required,
27   * see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-7>rfc9457 section 3.7</a>
28   * Given the JSON fields are not mandatory, this reporter simply extracts the body of the
29   * response without validation.
30   * A RFC 9457 message is detected by the content type {@value #CONTENT_TYPE_PROBLEM_DETAILS_JSON} in the response header.
31   *
32   * @param <T> The type of the response.
33   * @param <E> The base exception type to throw if the response is not a RFC9457 message.
34   * @param <R> The type of the request or request builder (which allows to modify headers)
35   * @see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-7>RFC 9457</a>
36   */
37  public abstract class RFC9457Reporter<T, E extends Exception, R> {
38      public static final String CONTENT_TYPE_PROBLEM_DETAILS_JSON = "application/problem+json";
39  
40      protected abstract boolean isRFC9457Message(T response);
41  
42      protected abstract int getStatusCode(T response);
43  
44      protected abstract String getReasonPhrase(T response);
45  
46      protected abstract String getBody(T response) throws IOException;
47  
48      /**
49       * Prepares the request to accept RFC 9457 responses.
50       * This involves setting/updating the "Accept" header to include "application/problem+json".
51       * @param request The request or request builder to prepare
52       * @see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-2>RFC 9457 section 3.2</a>
53       */
54      public abstract void prepareRequest(R request);
55  
56      protected boolean hasRFC9457ContentType(String contentType) {
57          if (contentType == null) {
58              return false;
59          }
60          // strip off parameters
61          int idx = contentType.indexOf(';');
62          if (idx > -1) {
63              contentType = contentType.substring(0, idx);
64          }
65          return CONTENT_TYPE_PROBLEM_DETAILS_JSON.equals(contentType);
66      }
67  
68      /**
69       * Generates a {@link HttpRFC9457Exception} if the response type is a RFC 9457 message.
70       * Otherwise, it throws the base exception
71       *
72       * @param response The response to check for RFC 9457 messages.
73       * @param baseException The base exception to throw if the response is not a RFC 9457 message.
74       */
75      public void generateException(T response, BiConsumerChecked<Integer, String, E> baseException)
76              throws E, HttpRFC9457Exception {
77          int statusCode = getStatusCode(response);
78          String reasonPhrase = getReasonPhrase(response);
79  
80          if (isRFC9457Message(response)) {
81              String body;
82              try {
83                  body = getBody(response);
84              } catch (IOException ignore) {
85                  // No body found but it is representing a RFC 9457 message due to the content type.
86                  throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE);
87              }
88  
89              if (body != null && !body.isEmpty()) {
90                  RFC9457Payload rfc9457Payload = RFC9457Parser.parse(body);
91                  throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload);
92              }
93              throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE);
94          }
95          baseException.accept(statusCode, reasonPhrase);
96      }
97  }