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.http.header; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.function.*; 022 023/** 024 * Represents an HTTP <c>Content-Range</c> header. 025 * 026 * <p> 027 * Where in a full body message this partial message belongs. 028 * 029 * <h5 class='section'>See Also:</h5><ul> 030 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a> 031 * </ul> 032 * 033 * @since 9.2.1 034 */ 035public class ContentRange extends HttpStringHeader { 036 037 /** The header name */ 038 public static final String NAME = "Content-Range"; 039 040 /** 041 * Constructor with an eager value. 042 * 043 * @param value The header value. May be <jk>null</jk>. 044 */ 045 public ContentRange(String value) { 046 super(NAME, value); 047 } 048 049 /** 050 * Constructor with a lazy value supplier. 051 * 052 * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>. 053 */ 054 public ContentRange(Supplier<String> valueSupplier) { 055 super(NAME, valueSupplier); 056 } 057 058 /** 059 * Static factory method with an eager value. 060 * 061 * @param value The header value. May be <jk>null</jk>. 062 * @return A new instance. Never <jk>null</jk>. 063 */ 064 public static ContentRange of(String value) { 065 return new ContentRange(value); 066 } 067 068 /** 069 * Static factory method with a lazy value supplier. 070 * 071 * @param valueSupplier Supplier for the header value. Must not be <jk>null</jk>. 072 * @return A new instance. Never <jk>null</jk>. 073 */ 074 public static ContentRange of(Supplier<String> valueSupplier) { 075 return new ContentRange(valueSupplier); 076 } 077 078 /** 079 * Creates a new {@link Builder} defaulting to the <c>bytes</c> range unit. 080 * 081 * @return A new builder. Never <jk>null</jk>. 082 */ 083 public static Builder create() { 084 return new Builder(); 085 } 086 087 //------------------------------------------------------------------------------------------------------------------ 088 // Inner types 089 //------------------------------------------------------------------------------------------------------------------ 090 091 /** 092 * Fluent builder for assembling an HTTP <c>Content-Range</c> header value. 093 * 094 * <p> 095 * Composes the three fields defined by 096 * <a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7233#section-4.2'>RFC 7233 §4.2</a> — the range unit 097 * (default <c>bytes</c>), the resolved <c>first-last</c> byte range (or <c>*</c> for an unsatisfied range), and the 098 * <c>complete-length</c> (or <c>*</c> when unknown) — into forms such as <c>bytes 0-1023/2048</c>, 099 * <c>bytes 0-1023/*</c>, and <c>bytes */2048</c>. 100 * 101 * <h5 class='section'>Example:</h5> 102 * <p class='bjava'> 103 * String <jv>value</jv> = ContentRange.<jsm>create</jsm>() 104 * .range(0, 1023) 105 * .length(2048) 106 * .build(); 107 * <jc>// => "bytes 0-1023/2048"</jc> 108 * </p> 109 * 110 * <h5 class='section'>See Also:</h5><ul> 111 * <li class='jc'>{@link ContentRange} 112 * <li class='extlink'><a class='doclink' href='https://www.rfc-editor.org/rfc/rfc7233'>RFC 7233 - HTTP/1.1 Range Requests</a> 113 * </ul> 114 * 115 * @since 10.0.0 116 */ 117 public static class Builder { 118 119 private String unit = "bytes"; 120 private Long start; 121 private Long end; 122 private boolean rangeUnsatisfied; 123 private Long completeLength; 124 private boolean lengthUnknown; 125 126 /** 127 * Sets the range unit (default <c>bytes</c>). 128 * 129 * @param value The range unit. Must not be <jk>null</jk> or blank. 130 * @return This object. 131 * @throws IllegalArgumentException If {@code value} is <jk>null</jk> or blank. 132 */ 133 public Builder unit(String value) { 134 reqnn("value", value); 135 var v = value.trim(); 136 if (v.isEmpty()) 137 throw iaex("range unit must not be blank"); 138 unit = v; 139 return this; 140 } 141 142 /** 143 * Sets the resolved <c>first-last</c> byte range. 144 * 145 * <p> 146 * Clears any previously-set {@link #unsatisfiedRange() unsatisfied} marker. 147 * 148 * @param startValue The first byte position (inclusive). Must be non-negative. 149 * @param endValue The last byte position (inclusive). Must be greater than or equal to {@code startValue}. 150 * @return This object. 151 * @throws IllegalArgumentException If the positions are negative or out of order. 152 */ 153 public Builder range(long startValue, long endValue) { 154 if (startValue < 0) 155 throw iaex("range start must be non-negative: %s", startValue); 156 if (endValue < startValue) 157 throw iaex("range end %s must be >= start %s", endValue, startValue); 158 start = startValue; 159 end = endValue; 160 rangeUnsatisfied = false; 161 return this; 162 } 163 164 /** 165 * Marks the range portion as unsatisfied, rendering it as <c>*</c> (used with a known complete length). 166 * 167 * @return This object. 168 */ 169 public Builder unsatisfiedRange() { 170 rangeUnsatisfied = true; 171 start = null; 172 end = null; 173 return this; 174 } 175 176 /** 177 * Sets the <c>complete-length</c> (the total size of the representation). 178 * 179 * <p> 180 * Clears any previously-set {@link #unknownLength() unknown-length} marker. 181 * 182 * @param value The complete length in units. Must be non-negative. 183 * @return This object. 184 * @throws IllegalArgumentException If {@code value} is negative. 185 */ 186 public Builder length(long value) { 187 if (value < 0) 188 throw iaex("complete-length must be non-negative: %s", value); 189 completeLength = value; 190 lengthUnknown = false; 191 return this; 192 } 193 194 /** 195 * Marks the complete length as unknown, rendering it as <c>*</c>. 196 * 197 * @return This object. 198 */ 199 public Builder unknownLength() { 200 lengthUnknown = true; 201 completeLength = null; 202 return this; 203 } 204 205 /** 206 * Builds the rendered <c>Content-Range</c> header value. 207 * 208 * @return The header value. Never <jk>null</jk>. 209 */ 210 public String build() { 211 var rangePart = rangeUnsatisfied || start == null ? "*" : start + "-" + end; 212 var lengthPart = lengthUnknown || completeLength == null ? "*" : completeLength.toString(); 213 return unit + " " + rangePart + "/" + lengthPart; 214 } 215 216 /** 217 * Builds a {@link ContentRange} header bean directly from this builder. 218 * 219 * @return A {@link ContentRange} carrying {@link #build()}. Never <jk>null</jk>. 220 */ 221 public ContentRange toHeader() { 222 return ContentRange.of(build()); 223 } 224 225 @Override 226 public String toString() { 227 return build(); 228 } 229 } 230}