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