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}