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.part;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.function.*;
022
023import org.apache.http.*;
024import org.apache.juneau.commons.httppart.*;
025import org.apache.juneau.http.classic.header.*;
026import org.apache.juneau.marshall.httppart.*;
027import org.apache.juneau.marshall.oapi.*;
028import org.apache.juneau.marshall.serializer.*;
029import org.apache.juneau.marshall.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/JuneauRestCommon">juneau-rest-common</a>
045 * </ul>
046 */
047@SuppressWarnings({
048   "java:S2160", // equals() inherited from BasicPart compares name+value; serializer field affects rendering not identity
049   "java:S3740", // Raw HttpPartSerializer/Supplier types used where serialized HTTP part value type cannot be statically parameterized
050   "java:S9149" // Public Juneau DSL factory; hiding parent of()/create() is intentional.
051})
052public class SerializedPart extends BasicPart {
053
054   /**
055    * Instantiates a new instance of this object.
056    *
057    * @param name The part name.
058    * @param value
059    *    The part value.
060    *    <br>Can be any POJO.
061    * @return A new {@link SerializedPart} object, never <jk>null</jk>.
062    */
063   public static SerializedPart of(String name, Object value) {
064      return new SerializedPart(name, value, null, null, null, false);
065   }
066
067   /**
068    * Instantiates a new instance of this object.
069    *
070    * @param name The part name.
071    * @param value
072    *    The part value supplier.
073    *    <br>Can be a supplier of any POJO.
074    * @return A new {@link SerializedPart} object, never <jk>null</jk>.
075    */
076   public static SerializedPart of(String name, Supplier<?> value) {
077      return new SerializedPart(name, value, null, null, null, false);
078   }
079
080   private final Object value;
081   private HttpPartType type;
082   private HttpPartSerializerSession serializer;
083
084   private HttpPartSchema schema = HttpPartSchema.DEFAULT;
085
086   private boolean skipIfEmpty;
087
088   /**
089    * Constructor.
090    *
091    * @param name The part name.
092    * @param value The POJO to serialize to The part value.
093    * @param type The HTTP part type.  Can be <jk>null</jk>.  The effect is implementation-dependent — see {@link HttpPartSerializerSession#write}.
094    * @param serializer
095    *    The serializer to use for serializing the value to a string value.
096    *    <br>Can be <jk>null</jk>, in which case the value is converted using a simple string conversion instead of being serialized.
097    * @param schema
098    *    The schema object that defines the format of the output.
099    *    <br>If <jk>null</jk>, defaults to the schema defined on the serializer.
100    *    <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
101    *    <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
102    * @param skipIfEmpty If value is a blank string, the value should return as <jk>null</jk>.
103    */
104   public SerializedPart(String name, Object value, HttpPartType type, HttpPartSerializerSession serializer, HttpPartSchema schema, boolean skipIfEmpty) {
105      super(name, value);
106      this.value = value;
107      this.type = type;
108      this.serializer = serializer;
109      this.schema = schema;
110      this.skipIfEmpty = skipIfEmpty;
111   }
112
113   /**
114    * Copy constructor.
115    *
116    * @param copyFrom The object to copy.  Must not be <jk>null</jk>.
117    */
118   protected SerializedPart(SerializedPart copyFrom) {
119      super(copyFrom);
120      this.value = copyFrom.value;
121      this.type = copyFrom.type;
122      this.serializer = copyFrom.serializer == null ? serializer : copyFrom.serializer;
123      this.schema = copyFrom.schema == null ? schema : copyFrom.schema;
124      this.skipIfEmpty = copyFrom.skipIfEmpty;
125   }
126
127   @Override /* Overridden from Headerable */
128   public SerializedHeader asHeader() {
129      return new SerializedHeader(getName(), value, serializer, schema, skipIfEmpty);
130   }
131
132   /**
133    * Creates a copy of this object.
134    *
135    * @return A new copy of this object.
136    */
137   public SerializedPart copy() {
138      return new SerializedPart(this);
139   }
140
141   /**
142    * Copies this bean and sets the serializer and schema on it.
143    *
144    * @param serializer The new serializer for the bean.  Can be <jk>null</jk> (the serializer is left unchanged).
145    * @param schema The new schema for the bean.  Can be <jk>null</jk> (the schema is left unchanged).
146    * @return Either a new bean with the serializer set, or this bean if
147    *    both values are <jk>null</jk> or the serializer and schema were already set.
148    */
149   public SerializedPart copyWith(HttpPartSerializerSession serializer, HttpPartSchema schema) {
150      if ((this.serializer == null && nn(serializer)) || (this.schema == null && nn(schema))) {
151         SerializedPart p = copy();
152         if (nn(serializer))
153            p.serializer(serializer);
154         if (nn(schema))
155            p.schema(schema);
156         return p;
157      }
158      return this;
159   }
160
161   @Override /* Overridden from NameValuePair */
162   public String getValue() {
163      try {
164         Object v = unwrap(value);
165         HttpPartSchema schema2 = this.schema == null ? HttpPartSchema.DEFAULT : this.schema;
166         var def = schema2.getDefault();
167         if (v == null && ((def == null && ! schema2.isRequired()) || (def == null && schema2.isAllowEmptyValue())))
168            return null;
169         if (ie(s(v)) && skipIfEmpty && def == null)
170            return null;
171         return serializer == null ? s(v) : serializer.write(type, schema2, v);
172      } catch (SchemaValidationException e) {
173         throw rex(e, "Validation error on request %s part '%s'='%s'", type, getName(), value);
174      } catch (SerializeException e) {
175         throw rex(e, "Serialization error on request %s part '%s'", type, getName());
176      }
177   }
178
179   /**
180    * Sets the schema object that defines the format of the output.
181    *
182    * @param value The new value for this property.  Can be <jk>null</jk> to fall back to {@link HttpPartSchema#DEFAULT} when the value is read.
183    * @return This object.
184    */
185   public SerializedPart schema(HttpPartSchema value) {
186      schema = value;
187      return this;
188   }
189
190   /**
191    * Sets the serializer to use for serializing the value to a string value.
192    *
193    * @param value The new value for this property.  Can be <jk>null</jk> (no-op).
194    * @return This object.
195    */
196   public SerializedPart serializer(HttpPartSerializer value) {
197      if (nn(value))
198         return serializer(value.getPartSession());
199      return this;
200   }
201
202   /**
203    * Sets the serializer to use for serializing the value to a string value.
204    *
205    * @param value The new value for this property.  Can be <jk>null</jk>, in which case the value is converted using a simple string conversion instead of being serialized.
206    * @return This object.
207    */
208   public SerializedPart serializer(HttpPartSerializerSession value) {
209      serializer = value;
210      return this;
211   }
212
213   /**
214    * Don't serialize this pair if the value is <jk>null</jk> or an empty string.
215    *
216    * @return This object.
217    */
218   public SerializedPart skipIfEmpty() {
219      return skipIfEmpty(true);
220   }
221
222   /**
223    * Don't serialize this pair if the value is <jk>null</jk> or an empty string.
224    *
225    * @param value The new value of this setting.
226    * @return This object.
227    */
228   public SerializedPart skipIfEmpty(boolean value) {
229      skipIfEmpty = value;
230      return this;
231   }
232
233   /**
234    * Sets the HTTP part type.
235    *
236    * @param value The new value for this property.  Can be <jk>null</jk>.  The effect is implementation-dependent — see {@link HttpPartSerializerSession#write}.
237    * @return This object.
238    */
239   public SerializedPart type(HttpPartType value) {
240      type = value;
241      return this;
242   }
243
244   private static Object unwrap(Object o) {
245      if (o instanceof Supplier o2)
246         return o2.get();
247      return o;
248   }
249}