001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.spi.connector.transport.http.RFC9457; 020 021import java.io.IOException; 022 023/** 024 * A reporter for RFC 9457 messages. 025 * RFC 9457 is a standard for reporting problems in HTTP responses as a JSON object. 026 * There are members specified in the RFC but none of those appear to be required, 027 * see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-7>rfc9457 section 3.7</a> 028 * Given the JSON fields are not mandatory, this reporter simply extracts the body of the 029 * response without validation. 030 * A RFC 9457 message is detected by the content type {@value #CONTENT_TYPE_PROBLEM_DETAILS_JSON} in the response header. 031 * 032 * @param <T> The type of the response. 033 * @param <E> The base exception type to throw if the response is not a RFC9457 message. 034 * @param <R> The type of the request or request builder (which allows to modify headers) 035 * @see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-7>RFC 9457</a> 036 */ 037public abstract class RFC9457Reporter<T, E extends Exception, R> { 038 public static final String CONTENT_TYPE_PROBLEM_DETAILS_JSON = "application/problem+json"; 039 public static final String CONTENT_TYPE_PROBLEM_DETAILS_JSON_AND_ANY = "application/problem+json,*/*"; 040 041 protected abstract boolean isRFC9457Message(T response); 042 043 protected abstract int getStatusCode(T response); 044 045 protected abstract String getReasonPhrase(T response); 046 047 protected abstract String getBody(T response) throws IOException; 048 049 /** 050 * Prepares the request to accept RFC 9457 responses. 051 * This involves setting/updating the "Accept" header to include "application/problem+json". 052 * @param request The request or request builder to prepare 053 * @see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-2>RFC 9457 section 3.2</a> 054 */ 055 public abstract void prepareRequest(R request); 056 057 protected boolean hasRFC9457ContentType(String contentType) { 058 if (contentType == null) { 059 return false; 060 } 061 // strip off parameters 062 int idx = contentType.indexOf(';'); 063 if (idx > -1) { 064 contentType = contentType.substring(0, idx); 065 } 066 return CONTENT_TYPE_PROBLEM_DETAILS_JSON.equals(contentType); 067 } 068 069 /** 070 * Generates a {@link HttpRFC9457Exception} if the response type is a RFC 9457 message. 071 * Otherwise, it throws the base exception 072 * 073 * @param response The response to check for RFC 9457 messages. 074 * @param baseException The base exception to throw if the response is not a RFC 9457 message. 075 */ 076 public void generateException(T response, BiConsumerChecked<Integer, String, E> baseException) 077 throws E, HttpRFC9457Exception { 078 int statusCode = getStatusCode(response); 079 String reasonPhrase = getReasonPhrase(response); 080 081 if (isRFC9457Message(response)) { 082 String body; 083 try { 084 body = getBody(response); 085 } catch (IOException ignore) { 086 // No body found but it is representing a RFC 9457 message due to the content type. 087 throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE); 088 } 089 090 if (body != null && !body.isEmpty()) { 091 RFC9457Payload rfc9457Payload = RFC9457Parser.parse(body); 092 throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload); 093 } 094 throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE); 095 } 096 baseException.accept(statusCode, reasonPhrase); 097 } 098}