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.*; 021import static org.apache.juneau.commons.utils.ThrowableUtils.*; 022import static org.apache.juneau.http.classic.HttpHeaders.*; 023 024import java.io.*; 025 026import org.apache.http.*; 027import org.apache.juneau.commons.io.*; 028import org.apache.juneau.http.UnmodifiableBean; 029import org.apache.juneau.http.classic.header.*; 030import org.apache.juneau.marshall.httppart.*; 031import org.apache.juneau.marshall.serializer.*; 032 033/** 034 * HttpEntity for serializing POJOs as the body of HTTP requests. 035 * 036 * <h5 class='section'>See Also:</h5><ul> 037 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 038 039 * </ul> 040 */ 041public class SerializedEntity extends BasicHttpEntity<SerializedEntity> { 042 Serializer serializer; 043 HttpPartSchema schema; 044 045 /** 046 * Constructor. 047 */ 048 public SerializedEntity() {} 049 050 /** 051 * Constructor. 052 * 053 * @param contentType The entity content type. Can be <jk>null</jk>, in which case the <c>Content-Type</c> header falls back to the serializer's primary media type (if a serializer is set) or is omitted. 054 * @param content The entity content. Can be <jk>null</jk>, in which case it's converted to an empty string if no serializer is set, or passed through to the serializer as-is otherwise. 055 * @param serializer The entity serializer. Can be <jk>null</jk>, in which case the content is written via its <c>toString()</c> value (or an empty string if the content is also <jk>null</jk>) instead of being serialized. 056 * @param schema The entity schema. Can be <jk>null</jk> (no schema is applied to the serializer). 057 */ 058 public SerializedEntity(ContentType contentType, Object content, Serializer serializer, HttpPartSchema schema) { 059 super(contentType, content); 060 this.serializer = serializer; 061 this.schema = schema; 062 } 063 064 /** 065 * Copy constructor. 066 * 067 * @param copyFrom The bean being copied. Must not be <jk>null</jk>. 068 */ 069 public SerializedEntity(SerializedEntity copyFrom) { 070 super(copyFrom); 071 this.serializer = copyFrom.serializer; 072 this.schema = copyFrom.schema; 073 } 074 075 /** 076 * Creates a builder for this class initialized with the contents of this bean. 077 * 078 * <p> 079 * Allows you to create a modifiable copy of this bean. 080 * 081 * @return A new builder bean. 082 */ 083 @Override /* Overridden from BasicHttpEntity */ 084 public SerializedEntity copy() { 085 return new SerializedEntity(this); 086 } 087 088 /** 089 * Copies this bean and sets the serializer and schema on it. 090 * 091 * @param serializer The new serializer for the bean. Can be <jk>null</jk> (the serializer is left unchanged). 092 * @param schema The new schema for the bean. Can be <jk>null</jk> (the schema is left unchanged). 093 * @return Either a new bean with the serializer set, or this bean if 094 * both values are <jk>null</jk> or the serializer and schema were already set. 095 */ 096 public SerializedEntity copyWith(Serializer serializer, HttpPartSchema schema) { 097 if ((this.serializer == null && nn(serializer)) || (this.schema == null && nn(schema))) { 098 SerializedEntity h = copy(); 099 if (nn(serializer)) 100 h.setSerializer(serializer); 101 if (nn(schema)) 102 h.setSchema(schema); 103 return h; 104 } 105 return this; 106 } 107 108 //----------------------------------------------------------------------------------------------------------------- 109 110 @Override /* Overridden from BasicHttpEntity */ 111 public InputStream getContent() { 112 var baos = new ByteArrayOutputStream(); 113 try { 114 writeTo(baos); 115 return new ByteArrayInputStream(baos.toByteArray()); 116 } catch (IOException e) { 117 throw toRex(e); 118 } 119 } 120 121 @Override /* Overridden from BasicHttpEntity */ 122 public long getContentLength() { return -1; } 123 124 @Override 125 public Header getContentType() { 126 Header x = super.getContentType(); 127 if (x == null && nn(serializer)) 128 x = contentType(serializer.getPrimaryMediaType()); 129 return x; 130 } 131 132 @Override /* Overridden from BasicHttpEntity */ 133 public boolean isRepeatable() { return true; } 134 135 /** 136 * Sets the schema on this entity bean. 137 * 138 * <p> 139 * Used to provide instructions to the serializer on how to serialize this object. 140 * 141 * @param value The entity schema, can be <jk>null</jk> (no schema is applied to the serializer). 142 * @return This object. 143 */ 144 public SerializedEntity setSchema(HttpPartSchema value) { 145 return modify(() -> schema = value); 146 } 147 148 /** 149 * Sets the serializer on this entity bean. 150 * 151 * @param value The entity serializer, can be <jk>null</jk>, in which case the content is written via its <c>toString()</c> value instead of being serialized. 152 * @return This object. 153 */ 154 public SerializedEntity setSerializer(Serializer value) { 155 return modify(() -> serializer = value); 156 } 157 158 @Override /* Overridden from BasicHttpEntity */ 159 public SerializedEntity unmodifiable() { 160 return this instanceof UnmodifiableBean ? this : new Unmodifiable(this); 161 } 162 163 @Override /* Overridden from HttpEntity */ 164 public void writeTo(OutputStream os) throws IOException { 165 try { 166 os = new NoCloseOutputStream(os); 167 Object o = contentOrElse(null); 168 if (serializer == null) { 169 try (Writer w = new OutputStreamWriter(os, UTF8)) { 170 w.write(o == null ? "" : o.toString()); 171 } 172 } else { 173 SerializerSession session = serializer.createSession().schema(schema).build(); 174 try (Closeable c = session.isWriterSerializer() ? new OutputStreamWriter(os, UTF8) : os) { 175 session.write(o, c); 176 } 177 } 178 } catch (SerializeException e) { 179 throw rex(e, "Serialization error on request body."); 180 } 181 } 182 183 @Override /* Overridden from Object */ 184 public boolean equals(Object o) { 185 // Leaf-type gate: keeps this entity from comparing equal to a different leaf with equal base content, while 186 // preserving content equality (SerializedEntity.Unmodifiable IS-A SerializedEntity, so bean.equals(bean.unmodifiable()) holds). 187 // serializer and schema are real configuration state (not derived caches), so they participate in equality. 188 // Neither Serializer nor HttpPartSchema overrides equals(), so those two are compared by reference (identity). 189 return o instanceof SerializedEntity x && super.equals(o) && eq(serializer, x.serializer) && eq(schema, x.schema); 190 } 191 192 @Override /* Overridden from Object */ 193 public int hashCode() { 194 // Fold in the leaf type plus the equality-relevant config state; serializer/schema hash by identity (reference). 195 return h(SerializedEntity.class, super.hashCode(), serializer, schema); 196 } 197 198 /** 199 * Unmodifiable point-in-time snapshot of the enclosing {@link SerializedEntity}. 200 * 201 * <p> 202 * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through 203 * {@code modify(...)}, this single override freezes the entire mutation surface. 204 */ 205 public static class Unmodifiable extends SerializedEntity implements UnmodifiableBean { 206 207 /** 208 * Constructor. 209 * 210 * @param copyFrom The entity to snapshot. Must not be <jk>null</jk>. 211 */ 212 @SuppressWarnings({ 213 "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans. 214 }) 215 protected Unmodifiable(SerializedEntity copyFrom) { 216 super(copyFrom); 217 freeze(); 218 } 219 220 @Override /* Overridden from BasicHttpEntity */ 221 protected SerializedEntity modify(Runnable mutation) { 222 throw uoex("Bean is unmodifiable."); 223 } 224 } 225}