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 040 protected abstract boolean isRFC9457Message(T response); 041 042 protected abstract int getStatusCode(T response); 043 044 protected abstract String getReasonPhrase(T response); 045 046 protected abstract String getBody(T response) throws IOException; 047 048 /** 049 * Prepares the request to accept RFC 9457 responses. 050 * This involves setting/updating the "Accept" header to include "application/problem+json". 051 * @param request The request or request builder to prepare 052 * @see <a href=https://www.rfc-editor.org/rfc/rfc9457#section-3-2>RFC 9457 section 3.2</a> 053 */ 054 public abstract void prepareRequest(R request); 055 056 protected boolean hasRFC9457ContentType(String contentType) { 057 if (contentType == null) { 058 return false; 059 } 060 // strip off parameters 061 int idx = contentType.indexOf(';'); 062 if (idx > -1) { 063 contentType = contentType.substring(0, idx); 064 } 065 return CONTENT_TYPE_PROBLEM_DETAILS_JSON.equals(contentType); 066 } 067 068 /** 069 * Generates a {@link HttpRFC9457Exception} if the response type is a RFC 9457 message. 070 * Otherwise, it throws the base exception 071 * 072 * @param response The response to check for RFC 9457 messages. 073 * @param baseException The base exception to throw if the response is not a RFC 9457 message. 074 */ 075 public void generateException(T response, BiConsumerChecked<Integer, String, E> baseException) 076 throws E, HttpRFC9457Exception { 077 int statusCode = getStatusCode(response); 078 String reasonPhrase = getReasonPhrase(response); 079 080 if (isRFC9457Message(response)) { 081 String body; 082 try { 083 body = getBody(response); 084 } catch (IOException ignore) { 085 // No body found but it is representing a RFC 9457 message due to the content type. 086 throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE); 087 } 088 089 if (body != null && !body.isEmpty()) { 090 RFC9457Payload rfc9457Payload = RFC9457Parser.parse(body); 091 throw new HttpRFC9457Exception(statusCode, reasonPhrase, rfc9457Payload); 092 } 093 throw new HttpRFC9457Exception(statusCode, reasonPhrase, RFC9457Payload.INSTANCE); 094 } 095 baseException.accept(statusCode, reasonPhrase); 096 } 097}