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.adapter; 018 019import org.apache.juneau.bean.rfc7807.*; 020import org.apache.juneau.http.response.*; 021 022/** 023 * Static adapters that convert Juneau HTTP error types into 024 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc7807">RFC 7807</a> {@link Problem} beans. 025 * 026 * <p> 027 * This adapter lives in {@code juneau-rest-common} (rather than the {@code juneau-bean-rfc7807} bean module) so the 028 * bean module stays free of any {@code juneau-rest-common} dependency. The package is co-located with the bean's 029 * package namespace so the cross-reference is discoverable from {@link Problem}'s javadoc. 030 * 031 * <h5 class='section'>Example:</h5> 032 * <p class='bjava'> 033 * <jk>try</jk> { 034 * ... 035 * } <jk>catch</jk> (NotFound <jv>e</jv>) { 036 * Problem <jv>p</jv> = ProblemAdapters.<jsm>fromException</jsm>(<jv>e</jv>); 037 * <jc>// p.getStatus() == 404, p.getTitle() == "Not Found", p.getDetail() == e.getMessage()</jc> 038 * } 039 * </p> 040 * 041 * <h5 class='section'>Notes:</h5> 042 * <ul class='spaced-list'> 043 * <li>{@code type} is intentionally left {@code null}. RFC 7807 §3.1 defines {@code about:blank} as the default 044 * when the field is absent; the bean serializes a {@code null} {@code type} as an omitted field rather than as 045 * {@code "type":"about:blank"} on the wire (preserves the absent-vs-explicit distinction). 046 * <li>{@code detail} is suppressed when the exception's message exactly matches the reason phrase — the bare 047 * <c><jk>new</jk> NotFound()</c> case where the message would just echo {@code "Not Found"}. 048 * </ul> 049 * 050 * <h5 class='section'>See Also:</h5><ul> 051 * <li class='jc'>{@link Problem} 052 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauBeanRfc7807">juneau-bean-rfc7807</a> 053 * </ul> 054 */ 055public final class ProblemAdapters { 056 057 private ProblemAdapters() {} 058 059 /** 060 * Builds a {@link Problem} from a {@link BasicHttpException} (or any subclass like {@code NotFound}, 061 * {@code InternalServerError}, etc). 062 * 063 * <p> 064 * Mapping: 065 * <ul class='spaced-list'> 066 * <li>{@code status} ← {@link BasicHttpException#getStatusCode()} 067 * <li>{@code title} ← {@link org.apache.juneau.http.HttpStatusLine#getReasonPhrase()} (may be {@code null}) 068 * <li>{@code detail} ← {@link BasicHttpException#getMessage()}, but only when it differs from the reason 069 * phrase; otherwise {@code null} so the bare <c><jk>new</jk> NotFound()</c> case omits a redundant 070 * {@code "detail":"Not Found"} field. 071 * <li>{@code type}, {@code instance} ← never set by this adapter (callers add them via the fluent setters 072 * on the returned bean if a {@code type} URI is appropriate). 073 * </ul> 074 * 075 * @param e The exception to adapt. May be <jk>null</jk>. 076 * @return A new {@link Problem} populated from the exception, or <jk>null</jk> if {@code e} is <jk>null</jk>. 077 */ 078 public static Problem fromException(BasicHttpException e) { 079 if (e == null) 080 return null; 081 var statusLine = e.getStatusLine(); 082 var reasonPhrase = statusLine == null ? null : statusLine.getReasonPhrase(); // HTT — statusLine is always set in BasicHttpException constructors 083 var message = e.getMessage(); 084 var detail = (message == null || message.equals(reasonPhrase)) ? null : message; // HTT — getMessage() always returns non-null (falls back to reasonPhrase) 085 return new Problem() 086 .setStatus(e.getStatusCode()) 087 .setTitle(reasonPhrase) 088 .setDetail(detail); 089 } 090}