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.part;
018
019import static org.apache.juneau.commons.utils.ThrowableUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.util.function.*;
023
024import org.apache.http.*;
025import org.apache.juneau.http.header.*;
026import org.apache.juneau.httppart.*;
027import org.apache.juneau.oapi.*;
028import org.apache.juneau.serializer.*;
029import org.apache.juneau.urlencoding.*;
030
031/**
032 * Subclass of {@link NameValuePair} for serializing POJOs as URL-encoded form post entries using the
033 * {@link UrlEncodingSerializer class}.
034 *
035 * <h5 class='section'>Example:</h5>
036 * <p class='bjava'>
037 *    NameValuePairs <jv>params</jv> = <jk>new</jk> NameValuePairs()
038 *       .append(<jk>new</jk> SerializedNameValuePair(<js>"myPojo"</js>, <jv>pojo</jv>, UrlEncodingSerializer.<jsf>DEFAULT_SIMPLE</jsf>))
039 *       .append(<jk>new</jk> BasicNameValuePair(<js>"someOtherParam"</js>, <js>"foobar"</js>));
040 *    <jv>request</jv>.setEntity(<jk>new</jk> UrlEncodedFormEntity(<jv>params</jv>));
041 * </p>
042 *
043 * <h5 class='section'>See Also:</h5><ul>
044 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommonBasics">juneau-rest-common Basics</a>
045 * </ul>
046 */
047public class SerializedPart extends BasicPart {
048   /**
049    * Instantiates a new instance of this object.
050    *
051    * @param name The part name.
052    * @param value
053    *    The part value.
054    *    <br>Can be any POJO.
055    * @return A new {@link SerializedPart} object, never <jk>null</jk>.
056    */
057   public static SerializedPart of(String name, Object value) {
058      return new SerializedPart(name, value, null, null, null, false);
059   }
060
061   /**
062    * Instantiates a new instance of this object.
063    *
064    * @param name The part name.
065    * @param value
066    *    The part value supplier.
067    *    <br>Can be a supplier of any POJO.
068    * @return A new {@link SerializedPart} object, never <jk>null</jk>.
069    */
070   public static SerializedPart of(String name, Supplier<?> value) {
071      return new SerializedPart(name, value, null, null, null, false);
072   }
073
074   private final Object value;
075   private HttpPartType type;
076   private HttpPartSerializerSession serializer;
077
078   private HttpPartSchema schema = HttpPartSchema.DEFAULT;
079
080   private boolean skipIfEmpty;
081
082   /**
083    * Constructor.
084    *
085    * @param name The part name.
086    * @param value The POJO to serialize to The part value.
087    * @param type The HTTP part type.
088    * @param serializer
089    *    The serializer to use for serializing the value to a string value.
090    * @param schema
091    *    The schema object that defines the format of the output.
092    *    <br>If <jk>null</jk>, defaults to the schema defined on the serializer.
093    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
094    *    <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
095    * @param skipIfEmpty If value is a blank string, the value should return as <jk>null</jk>.
096    */
097   public SerializedPart(String name, Object value, HttpPartType type, HttpPartSerializerSession serializer, HttpPartSchema schema, boolean skipIfEmpty) {
098      super(name, value);
099      this.value = value;
100      this.type = type;
101      this.serializer = serializer;
102      this.schema = schema;
103      this.skipIfEmpty = skipIfEmpty;
104   }
105
106   /**
107    * Copy constructor.
108    *
109    * @param copyFrom The object to copy.
110    */
111   protected SerializedPart(SerializedPart copyFrom) {
112      super(copyFrom);
113      this.value = copyFrom.value;
114      this.type = copyFrom.type;
115      this.serializer = copyFrom.serializer == null ? serializer : copyFrom.serializer;
116      this.schema = copyFrom.schema == null ? schema : copyFrom.schema;
117      this.skipIfEmpty = copyFrom.skipIfEmpty;
118   }
119
120   @Override /* Overridden from Headerable */
121   public SerializedHeader asHeader() {
122      return new SerializedHeader(getName(), value, serializer, schema, skipIfEmpty);
123   }
124
125   /**
126    * Creates a copy of this object.
127    *
128    * @return A new copy of this object.
129    */
130   public SerializedPart copy() {
131      return new SerializedPart(this);
132   }
133
134   /**
135    * Copies this bean and sets the serializer and schema on it.
136    *
137    * @param serializer The new serializer for the bean.  Can be <jk>null</jk>.
138    * @param schema The new schema for the bean.  Can be <jk>null</jk>.
139    * @return Either a new bean with the serializer set, or this bean if
140    *    both values are <jk>null</jk> or the serializer and schema were already set.
141    */
142   public SerializedPart copyWith(HttpPartSerializerSession serializer, HttpPartSchema schema) {
143      if ((this.serializer == null && nn(serializer)) || (this.schema == null && nn(schema))) {
144         SerializedPart p = copy();
145         if (nn(serializer))
146            p.serializer(serializer);
147         if (nn(schema))
148            p.schema(schema);
149         return p;
150      }
151      return this;
152   }
153
154   @Override /* Overridden from NameValuePair */
155   public String getValue() {
156      try {
157         Object v = unwrap(value);
158         HttpPartSchema schema = this.schema == null ? HttpPartSchema.DEFAULT : this.schema;
159         var def = schema.getDefault();
160         if (v == null) {
161            if ((def == null && ! schema.isRequired()) || (def == null && schema.isAllowEmptyValue()))
162               return null;
163         }
164         if (e(s(v)) && skipIfEmpty && def == null)
165            return null;
166         return serializer == null ? s(v) : serializer.serialize(type, schema, v);
167      } catch (SchemaValidationException e) {
168         throw rex(e, "Validation error on request {0} part ''{1}''=''{2}''", type, getName(), value);
169      } catch (SerializeException e) {
170         throw rex(e, "Serialization error on request {0} part ''{1}''", type, getName());
171      }
172   }
173
174   /**
175    * Sets the schema object that defines the format of the output.
176    *
177    * @param value The new value for this property.
178    * @return This object.
179    */
180   public SerializedPart schema(HttpPartSchema value) {
181      schema = value;
182      return this;
183   }
184
185   /**
186    * Sets the serializer to use for serializing the value to a string value.
187    *
188    * @param value The new value for this property.
189    * @return This object.
190    */
191   public SerializedPart serializer(HttpPartSerializer value) {
192      if (nn(value))
193         return serializer(value.getPartSession());
194      return this;
195   }
196
197   /**
198    * Sets the serializer to use for serializing the value to a string value.
199    *
200    * @param value The new value for this property.
201    * @return This object.
202    */
203   public SerializedPart serializer(HttpPartSerializerSession value) {
204      serializer = value;
205      return this;
206   }
207
208   /**
209    * Don't serialize this pair if the value is <jk>null</jk> or an empty string.
210    *
211    * @return This object.
212    */
213   public SerializedPart skipIfEmpty() {
214      return skipIfEmpty(true);
215   }
216
217   /**
218    * Don't serialize this pair if the value is <jk>null</jk> or an empty string.
219    *
220    * @param value The new value of this setting.
221    * @return This object.
222    */
223   public SerializedPart skipIfEmpty(boolean value) {
224      skipIfEmpty = value;
225      return this;
226   }
227
228   /**
229    * Sets the HTTP part type.
230    *
231    * @param value The new value for this property.
232    * @return This object.
233    */
234   public SerializedPart type(HttpPartType value) {
235      type = value;
236      return this;
237   }
238
239   private static Object unwrap(Object o) {
240      if (o instanceof Supplier o2)
241         return o2.get();
242      return o;
243   }
244}