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