001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.oapi;
014
015import static org.apache.juneau.httppart.HttpPartSchema.CollectionFormat.*;
016import static org.apache.juneau.httppart.HttpPartSchema.Format.*;
017import static org.apache.juneau.httppart.HttpPartSchema.Type.*;
018import static org.apache.juneau.internal.StringUtils.*;
019
020import java.lang.reflect.*;
021import java.util.*;
022
023import org.apache.juneau.*;
024import org.apache.juneau.httppart.*;
025import org.apache.juneau.internal.*;
026import org.apache.juneau.serializer.*;
027import org.apache.juneau.uon.*;
028
029/**
030 * Session object that lives for the duration of a single use of {@link OpenApiSerializer}.
031 *
032 * <p>
033 * This class is NOT thread safe.
034 * It is typically discarded after one-time use although it can be reused within the same thread.
035 */
036public class OpenApiSerializerSession extends UonSerializerSession {
037
038   //-------------------------------------------------------------------------------------------------------------------
039   // Predefined instances
040   //-------------------------------------------------------------------------------------------------------------------
041
042   // Cache these for faster lookup
043   private static final BeanContext BC = BeanContext.DEFAULT;
044   private static final ClassMeta<byte[]> CM_ByteArray = BC.getClassMeta(byte[].class);
045   private static final ClassMeta<String[]> CM_StringArray = BC.getClassMeta(String[].class);
046   private static final ClassMeta<Calendar> CM_Calendar = BC.getClassMeta(Calendar.class);
047   private static final ClassMeta<Long> CM_Long = BC.getClassMeta(Long.class);
048   private static final ClassMeta<Integer> CM_Integer = BC.getClassMeta(Integer.class);
049   private static final ClassMeta<Double> CM_Double = BC.getClassMeta(Double.class);
050   private static final ClassMeta<Float> CM_Float = BC.getClassMeta(Float.class);
051   private static final ClassMeta<Boolean> CM_Boolean = BC.getClassMeta(Boolean.class);
052
053   private static final HttpPartSchema DEFAULT_SCHEMA = HttpPartSchema.DEFAULT;
054
055   //-------------------------------------------------------------------------------------------------------------------
056   // Instance
057   //-------------------------------------------------------------------------------------------------------------------
058
059   private final OpenApiSerializer ctx;
060
061   /**
062    * Create a new session using properties specified in the context.
063    *
064    * @param ctx
065    *    The context creating this session object.
066    *    The context contains all the configuration settings for this object.
067    * @param args
068    *    Runtime session arguments.
069    */
070   protected OpenApiSerializerSession(OpenApiSerializer ctx, SerializerSessionArgs args) {
071      super(ctx, false, args);
072      this.ctx = ctx;
073   }
074
075   //-----------------------------------------------------------------------------------------------------------------
076   // Entry point methods
077   //-----------------------------------------------------------------------------------------------------------------
078
079   @Override /* Serializer */
080   protected void doSerialize(SerializerPipe out, Object o) throws Exception {
081      out.getWriter().write(serialize(HttpPartType.BODY, getSchema(), o));
082   }
083
084   @Override /* PartSerializer */
085   public String serialize(HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException {
086      return serialize(null, schema, value);
087   }
088
089   @Override /* PartSerializer */
090   public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException {
091
092      schema = ObjectUtils.firstNonNull(schema, DEFAULT_SCHEMA);
093      ClassMeta<?> type = getClassMetaForObject(value);
094      if (type == null)
095         type = object();
096      HttpPartSchema.Type t = schema.getType(type);
097      HttpPartSchema.Format f = schema.getFormat(type);
098      HttpPartSchema.CollectionFormat cf = schema.getCollectionFormat();
099
100      String out = null;
101
102      schema.validateOutput(value, ctx);
103
104      if (type.hasTransformTo(schema.getParsedType()) || schema.getParsedType().hasTransformFrom(type)) {
105         value = toType(value, schema.getParsedType());
106         type = schema.getParsedType();
107      }
108
109      if (type.isUri()) {
110         value = getUriResolver().resolve(value);
111         type = string();
112      }
113
114      if (value != null) {
115
116         if (t == STRING) {
117
118            if (f == BYTE)
119               out = base64Encode(toType(value, CM_ByteArray));
120            else if (f == BINARY)
121               out = toHex(toType(value, CM_ByteArray));
122            else if (f == BINARY_SPACED)
123               out = toSpacedHex(toType(value, CM_ByteArray));
124            else if (f == DATE)
125               out = toIsoDate(toType(value, CM_Calendar));
126            else if (f == DATE_TIME)
127               out = toIsoDateTime(toType(value, CM_Calendar));
128            else if (f == HttpPartSchema.Format.UON)
129               out = super.serialize(partType, schema, value);
130            else
131               out = toType(value, string());
132
133         } else if (t == ARRAY) {
134
135            if (cf == HttpPartSchema.CollectionFormat.UON)
136               out = super.serialize(partType, null, toList(partType, type, value, schema));
137            else {
138               List<String> l = new ArrayList<>();
139
140               HttpPartSchema items = schema.getItems();
141               ClassMeta<?> vt = getClassMetaForObject(value);
142
143               if (type.isArray()) {
144                  for (int i = 0; i < Array.getLength(value); i++)
145                     l.add(serialize(partType, items, Array.get(value, i)));
146               } else if (type.isCollection()) {
147                  for (Object o : (Collection<?>)value)
148                     l.add(serialize(partType, items, o));
149               } else if (vt.hasTransformTo(String[].class)) {
150                  String[] ss = toType(value, CM_StringArray);
151                  for (int i = 0; i < ss.length; i++)
152                     l.add(serialize(partType, items, ss[i]));
153               }
154
155               if (cf == PIPES)
156                  out = joine(l, '|');
157               else if (cf == SSV)
158                  out = join(l, ' ');
159               else if (cf == TSV)
160                  out = join(l, '\t');
161               else
162                  out = joine(l, ',');
163            }
164
165         } else if (t == BOOLEAN) {
166
167            if (f == HttpPartSchema.Format.UON)
168               out = super.serialize(partType, null, value);
169            else
170               out = asString(toType(value, CM_Boolean));
171
172         } else if (t == INTEGER) {
173
174            if (f == HttpPartSchema.Format.UON)
175               out = super.serialize(partType, null, value);
176            else if (f == INT64)
177               out = asString(toType(value, CM_Long));
178            else
179               out = asString(toType(value, CM_Integer));
180
181         } else if (t == NUMBER) {
182
183            if (f == HttpPartSchema.Format.UON)
184               out = super.serialize(partType, null, value);
185            else if (f == DOUBLE)
186               out = asString(toType(value, CM_Double));
187            else
188               out = asString(toType(value, CM_Float));
189
190         } else if (t == OBJECT) {
191
192            if (f == HttpPartSchema.Format.UON) {
193               out = super.serialize(partType, null, value);
194            } else if (schema.hasProperties() && type.isMapOrBean()) {
195               out = super.serialize(partType, null, toMap(partType, type, value, schema));
196            } else {
197               out = super.serialize(partType, null, value);
198            }
199
200         } else if (t == FILE) {
201            throw new SerializeException("File part not supported.");
202
203         } else if (t == NO_TYPE) {
204            // This should never be returned by HttpPartSchema.getType(ClassMeta).
205            throw new SerializeException("Invalid type.");
206         }
207      }
208
209      schema.validateInput(out);
210      if (out == null)
211         out = schema.getDefault();
212      if (out == null)
213         out = "null";
214      return out;
215   }
216
217   @SuppressWarnings("rawtypes")
218   private Map toMap(HttpPartType partType, ClassMeta<?> type, Object o, HttpPartSchema s) throws SerializeException, SchemaValidationException {
219      if (s == null)
220         s = DEFAULT_SCHEMA;
221      ObjectMap m = new ObjectMap();
222      if (type.isBean()) {
223         for (BeanPropertyValue p : toBeanMap(o).getValues(isTrimNullProperties())) {
224            if (p.getMeta().canRead()) {
225               Throwable t = p.getThrown();
226               if (t == null)
227                  m.put(p.getName(), toObject(partType, p.getValue(), s.getProperty(p.getName())));
228            }
229         }
230      } else {
231         for (Map.Entry e : (Set<Map.Entry>)((Map)o).entrySet())
232            m.put(asString(e.getKey()), toObject(partType, e.getValue(), s.getProperty(asString(e.getKey()))));
233      }
234      if (isSortMaps())
235         return sort(m);
236      return m;
237   }
238
239   @SuppressWarnings("rawtypes")
240   private Collection toList(HttpPartType partType, ClassMeta<?> type, Object o, HttpPartSchema s) throws SerializeException, SchemaValidationException {
241      if (s == null)
242         s = DEFAULT_SCHEMA;
243      ObjectList l = new ObjectList();
244      HttpPartSchema items = s.getItems();
245      if (type.isArray()) {
246         for (int i = 0; i < Array.getLength(o); i++)
247            l.add(toObject(partType, Array.get(o, i), items));
248      } else if (type.isCollection()) {
249         for (Object o2 : (Collection<?>)o)
250            l.add(toObject(partType, o2, items));
251      } else {
252         l.add(toObject(partType, o, items));
253      }
254      if (isSortCollections())
255         return sort(l);
256      return l;
257   }
258
259   @SuppressWarnings("rawtypes")
260   private Object toObject(HttpPartType partType, Object o, HttpPartSchema s) throws SerializeException, SchemaValidationException {
261      if (o == null)
262         return null;
263      if (s == null)
264         s = DEFAULT_SCHEMA;
265      ClassMeta cm = getClassMetaForObject(o);
266      HttpPartSchema.Type t = s.getType(cm);
267      HttpPartSchema.Format f = s.getFormat(cm);
268      HttpPartSchema.CollectionFormat cf = s.getCollectionFormat();
269
270      if (t == STRING) {
271         if (f == BYTE)
272            return base64Encode(toType(o, CM_ByteArray));
273         if (f == BINARY)
274            return toHex(toType(o, CM_ByteArray));
275         if (f == BINARY_SPACED)
276            return toSpacedHex(toType(o, CM_ByteArray));
277         if (f == DATE)
278            return toIsoDate(toType(o, CM_Calendar));
279         if (f == DATE_TIME)
280            return toIsoDateTime(toType(o, CM_Calendar));
281         return o;
282      } else if (t == ARRAY) {
283         Collection l = toList(partType, getClassMetaForObject(o), o, s);
284         if (cf == CSV)
285            return joine(l, ',');
286         if (cf == PIPES)
287            return joine(l, '|');
288         if (cf == SSV)
289            return join(l, ' ');
290         if (cf == TSV)
291            return join(l, '\t');
292         return l;
293      } else if (t == OBJECT) {
294         return toMap(partType, getClassMetaForObject(o), o, s);
295      }
296
297      return o;
298   }
299
300   private <T> T toType(Object in, ClassMeta<T> type) throws SerializeException {
301      try {
302         return convertToType(in, type);
303      } catch (InvalidDataConversionException e) {
304         throw new SerializeException(e.getMessage());
305      }
306   }
307}