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> -&gt; <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}