001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.bean.rfc7807; 018 019/** 020 * SPI for mapping a domain or framework {@link Throwable} into an 021 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807">RFC 7807 / 9457</a> {@link Problem} bean. 022 * 023 * <p> 024 * Implementations contribute domain-specific error translation without subclassing the request-processor chain. 025 * A {@code ProblemMapper} bean is registered via the resource bean store (e.g. a {@code @Bean ProblemMapper foo()} 026 * factory method on a {@code @Rest}-annotated resource, or any other mechanism that contributes named beans to 027 * the resource's bean store); the server-side processor picks it up automatically when an opted-in operation 028 * throws or returns a matching exception. 029 * 030 * <h5 class='section'>Discovery model</h5> 031 * <p> 032 * The processor collects all {@code ProblemMapper} beans visible from the resource's bean store, then picks the 033 * <b>most-specific</b> mapper for the exception class hierarchy (a mapper whose {@link #getExceptionType()} is the 034 * closest superclass of the thrown exception wins; ties are broken by registration order, with the bean-store 035 * walk order serving as the tiebreaker). Mappers that return {@code null} are skipped and the next-most-specific 036 * mapper is tried, finally falling back to the default {@code ProblemAdapters.fromException(...)} adaptation in 037 * {@code juneau-rest-common} for {@code BasicHttpException} subclasses. 038 * 039 * <h5 class='section'>Example:</h5> 040 * <p class='bjava'> 041 * <jk>public class</jk> MyDomainProblemMapper <jk>implements</jk> ProblemMapper<MyDomainException> { 042 * 043 * <ja>@Override</ja> 044 * <jk>public</jk> Class<MyDomainException> getExceptionType() { 045 * <jk>return</jk> MyDomainException.<jk>class</jk>; 046 * } 047 * 048 * <ja>@Override</ja> 049 * <jk>public</jk> Problem map(MyDomainException <jv>e</jv>) { 050 * <jk>return</jk> Problem.<jsm>fromStatus</jsm>(422, <js>"Domain rule violated"</js>, <jv>e</jv>.getMessage()) 051 * .setType(<jk>new</jk> URI(<js>"https://example.com/probs/domain"</js>)) 052 * .set(<js>"rule"</js>, <jv>e</jv>.getRuleId()); 053 * } 054 * } 055 * 056 * <ja>@Rest</ja>(problemDetails=<js>"true"</js>) 057 * <jk>public class</jk> MyResource { 058 * 059 * <jc>// Bean-store registration: factory method on the resource class.</jc> 060 * <ja>@Bean</ja> <jk>public</jk> ProblemMapper<MyDomainException> myMapper() { 061 * <jk>return new</jk> MyDomainProblemMapper(); 062 * } 063 * 064 * <ja>@RestGet</ja>(<js>"/buy"</js>) 065 * <jk>public</jk> Receipt buy() { 066 * <jk>throw new</jk> MyDomainException(...); <jc>// translated by myMapper()</jc> 067 * } 068 * } 069 * </p> 070 * 071 * <h5 class='section'>See Also:</h5><ul> 072 * <li class='jc'>{@link Problem} 073 * <li class='jc'>{@link ProblemException} 074 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerProblemDetails">RFC 7807 / 9457 Problem Details</a> 075 * </ul> 076 * 077 * @param <T> The {@link Throwable} subtype this mapper handles. 078 */ 079public interface ProblemMapper<T extends Throwable> { 080 081 /** 082 * Returns the exception type this mapper handles. 083 * 084 * <p> 085 * The processor matches mappers by checking 086 * {@code getExceptionType().isAssignableFrom(thrown.getClass())} for each registered mapper, then picks the 087 * mapper whose {@code getExceptionType()} sits closest to the thrown class in the hierarchy. Implementations 088 * usually return the same class their {@code <T>} parameter binds; declaring it explicitly here avoids 089 * reflective inspection of the generic type at runtime. 090 * 091 * @return The exception class this mapper handles. Must not be <jk>null</jk>. 092 */ 093 Class<T> getExceptionType(); 094 095 /** 096 * Maps the supplied exception to a {@link Problem} bean. 097 * 098 * <p> 099 * Returning <jk>null</jk> signals "no opinion on this exception"; the processor then tries the next-most-specific 100 * registered mapper (and, ultimately, the built-in default fallback). Returning a populated {@link Problem} 101 * causes the processor to emit that bean as {@code application/problem+json}. 102 * 103 * @param exception The exception to translate. Never <jk>null</jk>. 104 * @return The {@link Problem} bean to emit, or <jk>null</jk> to defer to the next mapper. 105 */ 106 Problem map(T exception); 107}