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 (the 006 * "License"); you may not use this file except in compliance with the 007 * 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.http.header; 018 019 020import static java.time.format.DateTimeFormatter.*; 021import static java.time.temporal.ChronoUnit.*; 022import static org.apache.juneau.commons.utils.Shorts.*; 023import static org.apache.juneau.commons.utils.StringUtils.*; 024 025import java.time.*; 026import java.util.*; 027import java.util.function.*; 028 029/** 030 * Represents an HTTP <c>Retry-After</c> header. 031 * 032 * <p> 033 * Delay before retrying, as a delta-seconds integer or an HTTP-date. 034 * 035 * <h5 class='section'>See Also:</h5><ul> 036 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a> 037 * </ul> 038 * 039 * @since 9.2.1 040 */ 041@SuppressWarnings({ 042 "java:S2160" // equals() on HttpHeaderBean uses name + getValue() 043}) 044public class RetryAfter extends HttpHeaderBean { 045 046 /** The header name */ 047 public static final String NAME = "Retry-After"; 048 049 private final Integer delaySeconds; 050 private final ZonedDateTime retryAt; 051 private final Supplier<?> supplier; 052 053 /** 054 * Constructor with delay seconds. 055 * 056 * @param value Seconds to wait. May be <jk>null</jk>. 057 */ 058 public RetryAfter(Integer value) { 059 super(NAME, (String)null); 060 this.delaySeconds = value; 061 this.retryAt = null; 062 this.supplier = null; 063 } 064 065 /** 066 * Constructor with a wire string (integer or HTTP-date). 067 * 068 * @param value The header value. May be <jk>null</jk>. 069 */ 070 public RetryAfter(String value) { 071 super(NAME, (String)null); 072 if (isNumeric(value)) { 073 this.delaySeconds = Integer.parseInt(value); 074 this.retryAt = null; 075 } else { 076 this.delaySeconds = null; 077 this.retryAt = ie(value) ? null : parseHttpDate(value); 078 } 079 this.supplier = null; 080 } 081 082 /** 083 * Constructor with a lazy supplier of {@link Integer} or {@link ZonedDateTime}. 084 * 085 * @param valueSupplier Supplier for the value. Must not be <jk>null</jk>. 086 */ 087 public RetryAfter(Supplier<?> valueSupplier) { 088 super(NAME, (String)null); 089 this.delaySeconds = null; 090 this.retryAt = null; 091 this.supplier = valueSupplier; 092 } 093 094 /** 095 * Constructor with an HTTP-date. 096 * 097 * @param value The retry time. May be <jk>null</jk>. 098 */ 099 public RetryAfter(ZonedDateTime value) { 100 super(NAME, (String)null); 101 this.delaySeconds = null; 102 this.retryAt = value; 103 this.supplier = null; 104 } 105 106 /** 107 * @return The value as seconds when encoded as a delta-seconds integer. 108 */ 109 public Optional<Integer> asInteger() { 110 if (nn(supplier)) { 111 var o = supplier.get(); 112 return o(o instanceof Integer o2 ? o2 : null); 113 } 114 return o(delaySeconds); 115 } 116 117 /** 118 * @return The value as {@link ZonedDateTime} when encoded as an HTTP-date. 119 */ 120 public Optional<ZonedDateTime> asZonedDateTime() { 121 if (nn(supplier)) { 122 var o = supplier.get(); 123 return o(o instanceof ZonedDateTime o2 ? o2 : null); 124 } 125 return o(retryAt); 126 } 127 128 @Override /* HttpHeader */ 129 public String getValue() { 130 if (nn(supplier)) { 131 var o = supplier.get(); 132 if (o == null) 133 return null; 134 if (o instanceof Integer o2) 135 return o2.toString(); 136 if (o instanceof ZonedDateTime o2) 137 return RFC_1123_DATE_TIME.format(o2); 138 throw rex("Invalid object type returned by supplier: %s", cn(o)); 139 } 140 if (nn(delaySeconds)) 141 return delaySeconds.toString(); 142 if (nn(retryAt)) 143 return RFC_1123_DATE_TIME.format(retryAt); 144 return null; 145 } 146 147 /** 148 * @param value Delay seconds. May be <jk>null</jk>, in which case <jk>null</jk> is returned. 149 * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>. 150 */ 151 public static RetryAfter of(Integer value) { 152 return value == null ? null : new RetryAfter(value); 153 } 154 155 /** 156 * @param value Wire string. May be <jk>null</jk>, in which case <jk>null</jk> is returned. 157 * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>. 158 */ 159 public static RetryAfter of(String value) { 160 return value == null ? null : new RetryAfter(value); 161 } 162 163 /** 164 * @param valueSupplier Supplier of {@link Integer} or {@link ZonedDateTime}. Must not be <jk>null</jk>. 165 * @return A new instance. Never <jk>null</jk>. 166 */ 167 public static RetryAfter of(Supplier<?> valueSupplier) { 168 return new RetryAfter(valueSupplier); 169 } 170 171 /** 172 * @param value HTTP-date. May be <jk>null</jk>, in which case <jk>null</jk> is returned. 173 * @return A new instance, or <jk>null</jk> if <c>value</c> is <jk>null</jk>. 174 */ 175 public static RetryAfter of(ZonedDateTime value) { 176 return value == null ? null : new RetryAfter(value); 177 } 178 179 private static ZonedDateTime parseHttpDate(String value) { 180 return ZonedDateTime.from(RFC_1123_DATE_TIME.parse(value)).truncatedTo(SECONDS); 181 } 182}