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.resource; 018 019import static org.apache.juneau.common.utils.Utils.*; 020 021import java.io.*; 022import java.util.function.*; 023 024import org.apache.http.*; 025import org.apache.juneau.annotation.*; 026import org.apache.juneau.assertions.*; 027import org.apache.juneau.http.entity.*; 028import org.apache.juneau.http.header.*; 029import org.apache.juneau.internal.*; 030 031/** 032 * A basic {@link org.apache.juneau.http.resource.HttpResource} implementation with additional features. 033 * 034 * Provides the following features: 035 * <ul class='spaced-list'> 036 * <li> 037 * Caching. 038 * <li> 039 * Fluent setters. 040 * <li> 041 * Fluent assertions. 042 * <li> 043 * Externally-supplied/dynamic content. 044 * </ul> 045 * 046 * <h5 class='section'>See Also:</h5><ul> 047 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommonBasics">juneau-rest-common Basics</a> 048 * </ul> 049 */ 050@BeanIgnore /* Use toString() to serialize */ 051public class BasicResource implements HttpResource { 052 053 //----------------------------------------------------------------------------------------------------------------- 054 // Instance 055 //----------------------------------------------------------------------------------------------------------------- 056 057 BasicHttpEntity entity; 058 HeaderList headers = HeaderList.create(); 059 boolean unmodifiable; 060 061 /** 062 * Constructor. 063 * 064 * @param entity The entity that makes up this resource content. 065 */ 066 public BasicResource(BasicHttpEntity entity) { 067 this.entity = entity; 068 } 069 070 /** 071 * Copy constructor. 072 * 073 * @param copyFrom The bean bean copied. 074 */ 075 public BasicResource(BasicResource copyFrom) { 076 this.entity = copyFrom.entity.copy(); 077 this.headers = copyFrom.headers.copy(); 078 } 079 080 081 /** 082 * Constructor. 083 * 084 * <p> 085 * This is the constructor used when parsing an HTTP response. 086 * 087 * @param response The HTTP response to copy from. Must not be <jk>null</jk>. 088 * @throws IOException Rethrown from {@link HttpEntity#getContent()}. 089 */ 090 public BasicResource(HttpResponse response) throws IOException { 091 this(new StreamEntity()); 092 copyFrom(response); 093 } 094 095 /** 096 * Creates a builder for this class initialized with the contents of this bean. 097 * 098 * <p> 099 * Allows you to create a modifiable copy of this bean. 100 * 101 * @return A new builder bean. 102 */ 103 public BasicResource copy() { 104 return new BasicResource(this); 105 } 106 107 /** 108 * Copies the contents of the specified HTTP response to this builder. 109 * 110 * @param response The response to copy from. Must not be null. 111 * @return This object. 112 * @throws IOException If content could not be retrieved. 113 */ 114 public BasicResource copyFrom(HttpResponse response) throws IOException { 115 addHeaders(response.getAllHeaders()); 116 setContent(response.getEntity().getContent()); 117 return this; 118 } 119 120 //----------------------------------------------------------------------------------------------------------------- 121 // Properties 122 //----------------------------------------------------------------------------------------------------------------- 123 124 125 /** 126 * Specifies whether this bean should be unmodifiable. 127 * <p> 128 * When enabled, attempting to set any properties on this bean will cause an {@link UnsupportedOperationException}. 129 * 130 * @return This object. 131 */ 132 public BasicResource setUnmodifiable() { 133 unmodifiable = true; 134 entity.setUnmodifiable(); 135 headers.setUnmodifiable(); 136 return this; 137 } 138 139 /** 140 * Returns <jk>true</jk> if this bean is unmodifiable. 141 * 142 * @return <jk>true</jk> if this bean is unmodifiable. 143 */ 144 public boolean isUnmodifiable() { 145 return unmodifiable; 146 } 147 148 /** 149 * Throws an {@link UnsupportedOperationException} if the unmodifiable flag is set on this bean. 150 */ 151 protected final void assertModifiable() { 152 if (unmodifiable) 153 throw new UnsupportedOperationException("Bean is read-only"); 154 } 155 156 /** 157 * Returns access to the underlying builder for the HTTP entity. 158 * 159 * @return The underlying builder for the HTTP entity. 160 */ 161 public HttpEntity getEntity() { 162 return entity; 163 } 164 165 /** 166 * Sets the content on this entity bean. 167 * 168 * @param value The entity content, can be <jk>null</jk>. 169 * @return This object. 170 */ 171 public BasicResource setContent(Object value) { 172 entity.setContent(value); 173 return this; 174 } 175 176 /** 177 * Sets the content on this entity bean from a supplier. 178 * 179 * <p> 180 * Repeatable entities such as {@link StringEntity} use this to allow the entity content to be resolved at 181 * serialization time. 182 * 183 * @param value The entity content, can be <jk>null</jk>. 184 * @return This object. 185 */ 186 public BasicResource setContent(Supplier<?> value) { 187 entity.setContent(value); 188 return this; 189 } 190 191 /** 192 * Sets the content type on this entity bean. 193 * 194 * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset. 195 * @return This object. 196 */ 197 public BasicResource setContentType(String value) { 198 entity.setContentType(value); 199 return this; 200 } 201 202 /** 203 * Sets the content type on this entity bean. 204 * 205 * @param value The new <c>Content-Type</c> header, or <jk>null</jk> to unset. 206 * @return This object. 207 */ 208 public BasicResource setContentType(ContentType value) { 209 entity.setContentType(value); 210 return this; 211 } 212 213 /** 214 * Sets the content length on this entity bean. 215 * 216 * @param value The new <c>Content-Length</c> header value, or <c>-1</c> to unset. 217 * @return This object. 218 */ 219 public BasicResource setContentLength(long value) { 220 entity.setContentLength(value); 221 return this; 222 } 223 224 /** 225 * Sets the content encoding header on this entity bean. 226 * 227 * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset. 228 * @return This object. 229 */ 230 public BasicResource setContentEncoding(String value) { 231 entity.setContentEncoding(value); 232 return this; 233 } 234 235 /** 236 * Sets the content encoding header on this entity bean. 237 * 238 * @param value The new <c>Content-Encoding</c> header, or <jk>null</jk> to unset. 239 * @return This object. 240 */ 241 public BasicResource setContentEncoding(ContentEncoding value) { 242 entity.setContentEncoding(value); 243 return this; 244 } 245 246 /** 247 * Sets the 'chunked' flag value to <jk>true</jk>. 248 * 249 * <h5 class='section'>Notes:</h5><ul> 250 * <li>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always 251 * use chunked encoding. 252 * </ul> 253 * 254 * @return This object. 255 */ 256 public BasicResource setChunked() { 257 entity.setChunked(); 258 return this; 259 } 260 261 /** 262 * Sets the 'chunked' flag value. 263 * 264 * <h5 class='section'>Notes:</h5><ul> 265 * <li class='note'>If the {@link HttpEntity#getContentLength()} method returns a negative value, the HttpClient code will always 266 * use chunked encoding. 267 * </ul> 268 * 269 * @param value The new value for this flag. 270 * @return This object. 271 */ 272 public BasicResource setChunked(boolean value) { 273 entity.setChunked(value); 274 return this; 275 } 276 277 /** 278 * Specifies that the contents of this resource should be cached into an internal byte array so that it can 279 * be read multiple times. 280 * 281 * @return This object. 282 * @throws IOException If entity could not be read into memory. 283 */ 284 public BasicResource setCached() throws IOException { 285 entity.setCached(); 286 return this; 287 } 288 289 //----------------------------------------------------------------------------------------------------------------- 290 // BasicHeaderGroup setters. 291 //----------------------------------------------------------------------------------------------------------------- 292 293 /** 294 * Sets the specified header in this builder. 295 * 296 * <p> 297 * This is a no-op if either the name or value is <jk>null</jk>. 298 * 299 * @param name The header name. 300 * @param value The header value. 301 * @return This object. 302 */ 303 public BasicResource setHeader(String name, String value) { 304 if (name != null && value != null) 305 headers.set(name, value); 306 return this; 307 } 308 309 /** 310 * Appends the specified header to the end of the headers in this builder. 311 * 312 * <p> 313 * This is a no-op if either the name or value is <jk>null</jk>. 314 * 315 * @param name The header name. 316 * @param value The header value. 317 * @return This object. 318 */ 319 public BasicResource addHeader(String name, String value) { 320 if (name != null && value != null) 321 headers.append(name, value); 322 return this; 323 } 324 325 /** 326 * Sets the specified headers in this builder. 327 * 328 * @param value The new value. 329 * @return This object. 330 */ 331 public BasicResource setHeaders(HeaderList value) { 332 headers = value.copy(); 333 return this; 334 } 335 336 /** 337 * Sets the specified headers in this builder. 338 * 339 * @param values The headers to add. <jk>null</jk> values are ignored. 340 * @return This object. 341 */ 342 public BasicResource setHeaders(Header...values) { 343 for (Header h : values) { 344 if (h != null) { 345 String n = h.getName(); 346 String v = h.getValue(); 347 if (isNotEmpty(n)) { 348 if (n.equalsIgnoreCase("content-type")) 349 setContentType(v); 350 else if (n.equalsIgnoreCase("content-encoding")) 351 setContentEncoding(v); 352 else if (n.equalsIgnoreCase("content-length")) 353 setContentLength(Long.parseLong(v)); 354 else 355 headers.set(h); 356 } 357 } 358 } 359 return this; 360 } 361 362 /** 363 * Appends the specified headers to the end of the headers in this builder. 364 * 365 * @param values The headers to set. <jk>null</jk> headers and headers with <jk>null</jk> names or values are ignored. 366 * @return This object. 367 */ 368 public BasicResource addHeaders(Header...values) { 369 for (Header h : values) { 370 if (h != null) { 371 String n = h.getName(); 372 String v = h.getValue(); 373 if (isNotEmpty(n)) { 374 if (n.equalsIgnoreCase("content-type")) 375 setContentType(v); 376 else if (n.equalsIgnoreCase("content-encoding")) 377 setContentEncoding(v); 378 else if (n.equalsIgnoreCase("content-length")) 379 setContentLength(Long.parseLong(v)); 380 else 381 headers.append(h); 382 } 383 } 384 } 385 return this; 386 } 387 388 //----------------------------------------------------------------------------------------------------------------- 389 // Other methods 390 //----------------------------------------------------------------------------------------------------------------- 391 392 /** 393 * Converts the contents of the entity of this resource as a string. 394 * 395 * <p> 396 * Note that this may exhaust the content on non-repeatable, non-cached entities. 397 * 398 * @return The contents of this entity as a string. 399 * @throws IOException If a problem occurred while trying to read the content. 400 */ 401 public String asString() throws IOException { 402 return entity.asString(); 403 } 404 405 /** 406 * Converts the contents of the entity of this resource as a byte array. 407 * 408 * <p> 409 * Note that this may exhaust the content on non-repeatable, non-cached entities. 410 * 411 * @return The contents of this entity as a byte array. 412 * @throws IOException If a problem occurred while trying to read the content. 413 */ 414 public byte[] asBytes() throws IOException { 415 return entity.asBytes(); 416 } 417 418 /** 419 * Returns an assertion on the contents of the entity of this resource. 420 * 421 * <p> 422 * Note that this may exhaust the content on non-repeatable, non-cached entities. 423 * 424 * @return A new fluent assertion. 425 * @throws IOException If a problem occurred while trying to read the byte array. 426 */ 427 public FluentStringAssertion<BasicResource> assertString() throws IOException { 428 return new FluentStringAssertion<>(asString(), this); 429 } 430 431 /** 432 * Returns an assertion on the contents of the entity of this resource. 433 * 434 * <p> 435 * Note that this may exhaust the content on non-repeatable, non-cached entities. 436 * 437 * @return A new fluent assertion. 438 * @throws IOException If a problem occurred while trying to read the byte array. 439 */ 440 public FluentByteArrayAssertion<BasicResource> assertBytes() throws IOException { 441 return new FluentByteArrayAssertion<>(asBytes(), this); 442 } 443 444 //----------------------------------------------------------------------------------------------------------------- 445 // Path-through methods. 446 //----------------------------------------------------------------------------------------------------------------- 447 448 @Override /* HttpEntity */ 449 public long getContentLength() { 450 return entity.getContentLength(); 451 } 452 453 @Override /* HttpEntity */ 454 public boolean isRepeatable() { 455 return entity.isRepeatable(); 456 } 457 458 @Override /* HttpEntity */ 459 public boolean isChunked() { 460 return entity.isChunked(); 461 } 462 463 @Override /* HttpEntity */ 464 public Header getContentType() { 465 return entity.getContentType(); 466 } 467 468 @Override /* HttpEntity */ 469 public Header getContentEncoding() { 470 return entity.getContentEncoding(); 471 } 472 473 @Override /* HttpEntity */ 474 public boolean isStreaming() { 475 return entity.isStreaming(); 476 } 477 478 @Override 479 public void consumeContent() throws IOException {} 480 481 @Override /* HttpEntity */ 482 public InputStream getContent() throws IOException, UnsupportedOperationException { 483 return entity.getContent(); 484 } 485 486 @Override /* HttpEntity */ 487 public void writeTo(OutputStream outStream) throws IOException { 488 entity.writeTo(outStream); 489 } 490 491 @Override /* HttpResource */ 492 public HeaderList getHeaders() { 493 return headers; 494 } 495}