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.classic.entity; 018 019import static org.apache.juneau.commons.utils.IoUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.io.*; 023import java.util.*; 024 025import org.apache.juneau.http.*; 026import org.apache.juneau.http.classic.header.*; 027 028/** 029 * A repeatable entity that obtains its content from a {@link File}. 030 * 031 * <h5 class='section'>See Also:</h5><ul> 032 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 033 * </ul> 034 */ 035@SuppressWarnings({ 036 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 037 "java:S9365", // Copy ctor delegates to super; remaining fields are content caches defaulted empty by design. 038 "resource" // Resource management handled externally 039}) 040public class FileEntity extends BasicHttpEntity<FileEntity> { 041 042 private byte[] byteCache; 043 private String stringCache; 044 045 /** 046 * Constructor. 047 */ 048 public FileEntity() {} 049 050 /** 051 * Constructor. 052 * 053 * @param contentType The entity content type. Can be <jk>null</jk> to omit an explicit <c>Content-Type</c> header. 054 * @param content The entity contents. Can be <jk>null</jk>, in which case a {@link NullPointerException} is thrown when the entity's content is read. 055 */ 056 public FileEntity(ContentType contentType, File content) { 057 super(contentType, content); 058 } 059 060 /** 061 * Copy constructor. 062 * 063 * @param copyFrom The bean being copied. Must not be <jk>null</jk>. 064 */ 065 protected FileEntity(FileEntity copyFrom) { 066 super(copyFrom); 067 } 068 069 @Override /* Overridden from AbstractHttpEntity */ 070 public byte[] asBytes() throws IOException { 071 if (isCached() && byteCache == null) 072 byteCache = readBytes(content(), getMaxLength()); 073 if (nn(byteCache)) 074 return cp(byteCache); 075 return readBytes(content()); 076 } 077 078 @Override /* Overridden from AbstractHttpEntity */ 079 public String asString() throws IOException { 080 if (isCached() && stringCache == null) 081 stringCache = read(new InputStreamReader(new FileInputStream(content()), getCharset()), getMaxLength()); 082 if (nn(stringCache)) 083 return stringCache; 084 return read(new InputStreamReader(new FileInputStream(content()), getCharset()), getMaxLength()); 085 } 086 087 @Override /* Overridden from BasicHttpEntity */ 088 public FileEntity copy() { 089 return new FileEntity(this); 090 } 091 092 @Override /* Overridden from HttpEntity */ 093 public InputStream getContent() throws IOException { 094 if (isCached()) 095 return new ByteArrayInputStream(asBytes()); 096 return new FileInputStream(content()); 097 } 098 099 @Override /* Overridden from HttpEntity */ 100 public long getContentLength() { return content().length(); } 101 102 @Override /* Overridden from HttpEntity */ 103 public boolean isRepeatable() { return true; } 104 105 @Override /* Overridden from BasicHttpEntity */ 106 public FileEntity unmodifiable() { 107 return this instanceof UnmodifiableBean ? this : new Unmodifiable(this); 108 } 109 110 @Override /* Overridden from HttpEntity */ 111 public void writeTo(OutputStream out) throws IOException { 112 reqnn("out", out); 113 114 if (isCached()) { 115 out.write(asBytes()); 116 } else { 117 try (var is = getContent()) { 118 pipe(is, out, getMaxLength()); 119 } 120 } 121 } 122 123 @Override /* Overridden from Object */ 124 public boolean equals(Object o) { 125 // Leaf-type gate: keeps this entity from comparing equal to a different leaf with equal base content, while 126 // preserving content equality (FileEntity.Unmodifiable IS-A FileEntity, so bean.equals(bean.unmodifiable()) still holds). 127 // byteCache/stringCache are memoized derivations of the inherited content/charset and are excluded from equality. 128 return o instanceof FileEntity && super.equals(o); 129 } 130 131 @Override /* Overridden from Object */ 132 public int hashCode() { 133 // Fold in the leaf type so distinct leaves land in different buckets; stable across FileEntity and its 134 // Unmodifiable snapshot (both report FileEntity.class), keeping the equals/hashCode contract intact. 135 return h(FileEntity.class, super.hashCode()); 136 } 137 138 private File content() { 139 var f = contentOrElse((File)null); 140 Objects.requireNonNull(f, "File"); 141 if (! f.exists()) 142 throw isex("File %s does not exist.", f.getAbsolutePath()); 143 if (! f.canRead()) 144 throw isex("File %s is not readable.", f.getAbsolutePath()); 145 return f; 146 } 147 148 /** 149 * Unmodifiable point-in-time snapshot of the enclosing {@link FileEntity}. 150 * 151 * <p> 152 * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through 153 * {@code modify(...)}, this single override freezes the entire mutation surface. 154 */ 155 public static class Unmodifiable extends FileEntity implements UnmodifiableBean { 156 157 /** 158 * Constructor. 159 * 160 * @param copyFrom The entity to snapshot. Must not be <jk>null</jk>. 161 */ 162 @SuppressWarnings({ 163 "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans. 164 }) 165 protected Unmodifiable(FileEntity copyFrom) { 166 super(copyFrom); 167 freeze(); 168 } 169 170 @Override /* Overridden from BasicHttpEntity */ 171 protected FileEntity modify(Runnable mutation) { 172 throw uoex("Bean is unmodifiable."); 173 } 174 } 175}