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.rest.client; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.net.*; 022import java.time.*; 023import java.util.*; 024 025import org.apache.juneau.commons.http.*; 026import org.apache.juneau.http.header.*; 027 028/** 029 * A fluent accessor for a named HTTP response header. 030 * 031 * <p> 032 * Provides convenient typed-access methods for reading header values. 033 * Returns {@code null} or empty results when the named header is absent from the response. 034 * 035 * <p> 036 * Obtain instances via {@link RestResponse#header(String)}. 037 * 038 * <h5 class='section'>See Also:</h5><ul> 039 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a> 040 * </ul> 041 * 042 * @since 9.2.1 043 */ 044@SuppressWarnings({ 045 "resource" // Eclipse resource analysis: response is borrowed; caller closes it 046}) 047public final class ResponseHeader { 048 049 private final String name; 050 051 private final RestResponse response; 052 053 ResponseHeader(String name, RestResponse response) { 054 this.name = name; 055 this.response = response; 056 } 057 058 /** 059 * Returns the header name. 060 * 061 * @return The header name. Never <jk>null</jk>. 062 */ 063 public String getName() { 064 return name; 065 } 066 067 /** 068 * Returns <jk>true</jk> if this header is present in the response. 069 * 070 * @return <jk>true</jk> if present. 071 */ 072 public boolean isPresent() { 073 return response.getFirstHeader(name) != null; 074 } 075 076 /** 077 * Returns the first value of this header, or <jk>null</jk> if absent. 078 * 079 * @return The header value, possibly <jk>null</jk>. 080 */ 081 public String getValue() { 082 var h = response.getFirstHeader(name); 083 return h != null ? h.value() : null; 084 } 085 086 /** 087 * Returns the first value of this header, or the given default if absent. 088 * 089 * @param defaultValue The default value to return when the header is absent. 090 * @return The header value, or {@code defaultValue} if absent. 091 */ 092 public String orElse(String defaultValue) { 093 var value = getValue(); 094 return or(value, defaultValue); 095 } 096 097 /** 098 * Returns the first value of this header as an {@link Optional}. 099 * 100 * @return An optional containing the first header value, or empty if absent. 101 */ 102 public Optional<String> asOptional() { 103 return o(getValue()); 104 } 105 106 /** 107 * Returns the first value of this header parsed as an {@code int}, or {@code -1} if absent or not parseable. 108 * 109 * @return The integer value, or {@code -1} if absent or not an integer. 110 */ 111 public int asInteger() { 112 var value = getValue(); 113 if (value == null) 114 return -1; 115 try { 116 return Integer.parseInt(value.trim()); 117 } catch (NumberFormatException e) { 118 return -1; 119 } 120 } 121 122 /** 123 * Returns the first value of this header parsed as a {@code long}, or {@code -1L} if absent or not parseable. 124 * 125 * @return The long value, or {@code -1L} if absent or not a long. 126 */ 127 public long asLong() { 128 var value = getValue(); 129 if (value == null) 130 return -1L; 131 try { 132 return Long.parseLong(value.trim()); 133 } catch (NumberFormatException e) { 134 return -1L; 135 } 136 } 137 138 /** 139 * Returns all values of this header. 140 * 141 * <p> 142 * For repeated headers, this returns each header value in encounter order. 143 * 144 * @return An unmodifiable list of header values. Never <jk>null</jk>, but possibly empty. 145 */ 146 public List<String> getValues() { 147 return response.getHeaders().stream() 148 .filter(h -> eqic(name, h.name())) 149 .map(TransportHeader::value) 150 .toList(); 151 } 152 153 /** 154 * Returns the first value as a comma-split list of individual tokens. 155 * 156 * <p> 157 * Useful for headers like {@code Accept} or {@code Allow} that combine multiple values with commas. 158 * 159 * @return A list of trimmed tokens. Never <jk>null</jk>, but possibly empty. 160 */ 161 public List<String> asCsvList() { 162 var value = getValue(); 163 if (value == null) 164 return List.of(); 165 return Arrays.stream(value.split(",")) 166 .map(String::trim) 167 .filter(s -> !s.isEmpty()) 168 .toList(); 169 } 170 171 /** 172 * Returns the first value of this header parsed as a {@link Boolean}. 173 * 174 * @return The boolean value, or empty if absent. 175 */ 176 public Optional<Boolean> asBoolean() { 177 var value = getValue(); 178 return value == null ? oe() : o(Boolean.parseBoolean(value.trim())); 179 } 180 181 /** 182 * Returns the first value of this header parsed as an RFC-1123 HTTP date. 183 * 184 * @return The parsed date, or empty if absent or not parseable. 185 */ 186 public Optional<ZonedDateTime> asZonedDateTime() { 187 var value = getValue(); 188 if (value == null) 189 return oe(); 190 try { 191 return HttpDateHeader.of(name, value).asZonedDateTime(); 192 } catch (DateTimeException e) { 193 return oe(); 194 } 195 } 196 197 /** 198 * Returns the first value of this header parsed as a {@link URI}. 199 * 200 * @return The parsed URI, or empty if absent or not a valid URI. 201 */ 202 public Optional<URI> asUri() { 203 var value = getValue(); 204 if (value == null) 205 return oe(); 206 try { 207 return o(new URI(value)); 208 } catch (URISyntaxException e) { 209 return oe(); 210 } 211 } 212 213 /** 214 * Returns the first value of this header parsed as a single {@link EntityTag} validator (e.g. {@code ETag}). 215 * 216 * @return The parsed entity tag, or empty if absent. 217 */ 218 public Optional<EntityTag> asEntityTag() { 219 try { 220 return o(EntityTag.of(getValue())); 221 } catch (IllegalArgumentException e) { 222 return oe(); 223 } 224 } 225 226 /** 227 * Returns the first value of this header parsed as a comma-delimited list of {@link EntityTag} validators 228 * (e.g. {@code If-Match}). 229 * 230 * @return The parsed entity tags, or {@link EntityTags#EMPTY} if absent or not parseable. Never <jk>null</jk>. 231 */ 232 public EntityTags asEntityTags() { 233 try { 234 return EntityTags.of(getValue()); 235 } catch (IllegalArgumentException e) { 236 return EntityTags.EMPTY; 237 } 238 } 239 240 /** 241 * Returns the first value of this header parsed as a q-valued list of string ranges (e.g. {@code Accept-Encoding}). 242 * 243 * @return The parsed string ranges. Never <jk>null</jk>, but possibly empty. 244 */ 245 public StringRanges asStringRanges() { 246 return StringRanges.of(getValue()); 247 } 248 249 @Override /* Object */ 250 public String toString() { 251 var value = getValue(); 252 return value != null ? name + ": " + value : name + ": <absent>"; 253 } 254}