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.console.bus; 018 019import java.util.*; 020 021import org.apache.juneau.marshall.collections.*; 022 023/** 024 * A named refusal from the bus: the session POST's {@code 400/403/429/501} answers and the codec's 025 * {@code bus:bad-frame}. The message is fixed server text; {@link #denied()} lists the topic names the caller requested, which is the only caller input in a refusal. 026 * 027 * <h5 class='section'>Example:</h5> 028 * <p class='bjava'> 029 * <jk>try</jk> { 030 * BusSession <jv>s</jv> = <jv>bus</jv>.openSession(<jv>req</jv>, <jv>body</jv>); 031 * ... 032 * } <jk>catch</jk> (BusRefusal <jv>e</jv>) { 033 * <jv>res</jv>.setStatus(<jv>e</jv>.status()); 034 * <jv>e</jv>.retryAfterSeconds().ifPresent(<jv>s</jv> -> <jv>res</jv>.setHeader(<js>"Retry-After"</js>, String.<jsm>valueOf</jsm>(<jv>s</jv>))); 035 * <jk>return</jk> <jv>e</jv>.toJson(); <jc>// {"code":"bus:topic-denied","message":"...","denied":["ops.secret"]}</jc> 036 * } 037 * </p> 038 * 039 * @since 10.0.0 040 */ 041public final class BusRefusal extends Exception { 042 043 private static final long serialVersionUID = 1L; 044 045 /** {@code Retry-After} seconds sent with {@code bus:too-many-sessions}. */ 046 public static final int TOO_MANY_SESSIONS_RETRY_AFTER_SECONDS = 5; 047 048 private final int status; 049 private final String code; 050 private final List<String> denied; 051 private final int retryAfterSeconds; 052 053 /** 054 * Constructor. 055 * 056 * @param status The HTTP status. 057 * @param code The {@code bus:*} code. 058 * @param message Fixed server text. 059 * @param denied The denied topics, for {@code bus:topic-denied}. Can be <jk>null</jk>. 060 * @param retryAfterSeconds {@code Retry-After} seconds, or {@code 0} for none. 061 */ 062 public BusRefusal(int status, String code, String message, List<String> denied, int retryAfterSeconds) { 063 super(message, null, false, false); 064 this.status = status; 065 this.code = code; 066 this.denied = denied == null ? List.of() : List.copyOf(denied); 067 this.retryAfterSeconds = retryAfterSeconds; 068 } 069 070 static BusRefusal badRequest(String message) { 071 return new BusRefusal(400, "bus:bad-request", message, null, 0); 072 } 073 074 static BusRefusal badFrame(String message) { 075 return new BusRefusal(400, "bus:bad-frame", message, null, 0); 076 } 077 078 static BusRefusal topicDenied(List<String> denied) { 079 return new BusRefusal(403, "bus:topic-denied", "the bus policy denies a requested topic", denied, 0); 080 } 081 082 static BusRefusal tooManySessions() { 083 return new BusRefusal(429, "bus:too-many-sessions", "too many bus sessions", null, TOO_MANY_SESSIONS_RETRY_AFTER_SECONDS); 084 } 085 086 static BusRefusal transportUnavailable() { 087 return new BusRefusal(501, "bus:transport-unavailable", "the websocket transport is not registered", null, 0); 088 } 089 090 /** @return The HTTP status. */ 091 public int status() { return status; } 092 093 /** @return The {@code bus:*} code. */ 094 public String code() { return code; } 095 096 /** @return The denied topics (empty unless {@code bus:topic-denied}). */ 097 public List<String> denied() { return denied; } 098 099 /** @return The {@code Retry-After} seconds, if any. */ 100 public OptionalInt retryAfterSeconds() { 101 return retryAfterSeconds > 0 ? OptionalInt.of(retryAfterSeconds) : OptionalInt.empty(); 102 } 103 104 /** 105 * The refusal body: {@code {code, message, denied?}}. 106 * 107 * @return A new map. 108 */ 109 public JsonMap toJson() { 110 var m = JsonMap.of("code", code, "message", getMessage()); 111 if (! denied.isEmpty()) 112 m.put("denied", denied); 113 return m; 114 } 115}