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.msgpack;
018
019import static org.apache.juneau.common.utils.IOUtils.*;
020
021import java.io.*;
022import java.lang.reflect.*;
023import java.util.*;
024import java.util.function.*;
025
026import org.apache.juneau.*;
027import org.apache.juneau.common.utils.*;
028import org.apache.juneau.httppart.*;
029import org.apache.juneau.internal.*;
030import org.apache.juneau.serializer.*;
031import org.apache.juneau.svl.*;
032import org.apache.juneau.swap.*;
033
034/**
035 * Session object that lives for the duration of a single use of {@link MsgPackSerializer}.
036 *
037 * <h5 class='section'>Notes:</h5><ul>
038 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
039 * </ul>
040 *
041 * <h5 class='section'>See Also:</h5><ul>
042 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MessagePackBasics">MessagePack Basics</a>
043
044 * </ul>
045 */
046public class MsgPackSerializerSession extends OutputStreamSerializerSession {
047
048   //-----------------------------------------------------------------------------------------------------------------
049   // Static
050   //-----------------------------------------------------------------------------------------------------------------
051
052   /**
053    * Creates a new builder for this object.
054    *
055    * @param ctx The context creating this session.
056    * @return A new builder.
057    */
058   public static Builder create(MsgPackSerializer ctx) {
059      return new Builder(ctx);
060   }
061
062   //-----------------------------------------------------------------------------------------------------------------
063   // Builder
064   //-----------------------------------------------------------------------------------------------------------------
065
066   /**
067    * Builder class.
068    */
069   public static class Builder extends OutputStreamSerializerSession.Builder {
070
071      MsgPackSerializer ctx;
072
073      /**
074       * Constructor
075       *
076       * @param ctx The context creating this session.
077       */
078      protected Builder(MsgPackSerializer ctx) {
079         super(ctx);
080         this.ctx = ctx;
081      }
082
083      @Override
084      public MsgPackSerializerSession build() {
085         return new MsgPackSerializerSession(this);
086      }
087      @Override /* Overridden from Builder */
088      public <T> Builder apply(Class<T> type, Consumer<T> apply) {
089         super.apply(type, apply);
090         return this;
091      }
092
093      @Override /* Overridden from Builder */
094      public Builder debug(Boolean value) {
095         super.debug(value);
096         return this;
097      }
098
099      @Override /* Overridden from Builder */
100      public Builder properties(Map<String,Object> value) {
101         super.properties(value);
102         return this;
103      }
104
105      @Override /* Overridden from Builder */
106      public Builder property(String key, Object value) {
107         super.property(key, value);
108         return this;
109      }
110
111      @Override /* Overridden from Builder */
112      public Builder unmodifiable() {
113         super.unmodifiable();
114         return this;
115      }
116
117      @Override /* Overridden from Builder */
118      public Builder locale(Locale value) {
119         super.locale(value);
120         return this;
121      }
122
123      @Override /* Overridden from Builder */
124      public Builder localeDefault(Locale value) {
125         super.localeDefault(value);
126         return this;
127      }
128
129      @Override /* Overridden from Builder */
130      public Builder mediaType(MediaType value) {
131         super.mediaType(value);
132         return this;
133      }
134
135      @Override /* Overridden from Builder */
136      public Builder mediaTypeDefault(MediaType value) {
137         super.mediaTypeDefault(value);
138         return this;
139      }
140
141      @Override /* Overridden from Builder */
142      public Builder timeZone(TimeZone value) {
143         super.timeZone(value);
144         return this;
145      }
146
147      @Override /* Overridden from Builder */
148      public Builder timeZoneDefault(TimeZone value) {
149         super.timeZoneDefault(value);
150         return this;
151      }
152
153      @Override /* Overridden from Builder */
154      public Builder javaMethod(Method value) {
155         super.javaMethod(value);
156         return this;
157      }
158
159      @Override /* Overridden from Builder */
160      public Builder resolver(VarResolverSession value) {
161         super.resolver(value);
162         return this;
163      }
164
165      @Override /* Overridden from Builder */
166      public Builder schema(HttpPartSchema value) {
167         super.schema(value);
168         return this;
169      }
170
171      @Override /* Overridden from Builder */
172      public Builder schemaDefault(HttpPartSchema value) {
173         super.schemaDefault(value);
174         return this;
175      }
176
177      @Override /* Overridden from Builder */
178      public Builder uriContext(UriContext value) {
179         super.uriContext(value);
180         return this;
181      }
182   }
183
184   //-----------------------------------------------------------------------------------------------------------------
185   // Instance
186   //-----------------------------------------------------------------------------------------------------------------
187
188   private final MsgPackSerializer ctx;
189
190   /**
191    * Constructor.
192    *
193    * @param builder The builder for this object.
194    */
195   protected MsgPackSerializerSession(Builder builder) {
196      super(builder);
197      ctx = builder.ctx;
198   }
199
200   @Override /* SerializerSession */
201   protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException {
202      serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null);
203   }
204
205   /*
206    * Converts the specified output target object to an {@link MsgPackOutputStream}.
207    */
208   private static MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws IOException {
209      Object output = out.getRawOutput();
210      if (output instanceof MsgPackOutputStream)
211         return (MsgPackOutputStream)output;
212      MsgPackOutputStream os = new MsgPackOutputStream(out.getOutputStream());
213      out.setOutputStream(os);
214      return os;
215   }
216
217   /*
218    * Workhorse method.
219    * Determines the type of object, and then calls the appropriate type-specific serialization method.
220    */
221   @SuppressWarnings({ "rawtypes" })
222   private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException {
223
224      if (o == null)
225         return out.appendNull();
226
227      if (eType == null)
228         eType = object();
229
230      ClassMeta<?> aType;        // The actual type
231      ClassMeta<?> sType;        // The serialized type
232
233      aType = push2(attrName, o, eType);
234      boolean isRecursion = aType == null;
235
236      // Handle recursion
237      if (aType == null)
238         return out.appendNull();
239
240      // Handle Optional<X>
241      if (isOptional(aType)) {
242         o = getOptionalValue(o);
243         eType = getOptionalType(eType);
244         aType = getClassMetaForObject(o, object());
245      }
246
247      sType = aType;
248      String typeName = getBeanTypeName(this, eType, aType, pMeta);
249
250      // Swap if necessary
251      ObjectSwap swap = aType.getSwap(this);
252      if (swap != null) {
253         o = swap(swap, o);
254         sType = swap.getSwapClassMeta(this);
255
256         // If the getSwapClass() method returns Object, we need to figure out
257         // the actual type now.
258         if (sType.isObject())
259            sType = getClassMetaForObject(o);
260      }
261
262      // '\0' characters are considered null.
263      if (o == null || (sType.isChar() && ((Character)o).charValue() == 0))
264         out.appendNull();
265      else if (sType.isBoolean())
266         out.appendBoolean((Boolean)o);
267      else if (sType.isNumber())
268         out.appendNumber((Number)o);
269      else if (sType.isBean())
270         serializeBeanMap(out, toBeanMap(o), typeName);
271      else if (sType.isUri() || (pMeta != null && pMeta.isUri()))
272         out.appendString(resolveUri(o.toString()));
273      else if (sType.isMap()) {
274         if (o instanceof BeanMap)
275            serializeBeanMap(out, (BeanMap)o, typeName);
276         else
277            serializeMap(out, (Map)o, eType);
278      }
279      else if (sType.isCollection()) {
280         serializeCollection(out, (Collection) o, eType);
281      }
282      else if (sType.isByteArray()) {
283         out.appendBinary((byte[])o);
284      }
285      else if (sType.isArray()) {
286         serializeCollection(out, toList(sType.getInnerClass(), o), eType);
287      }
288      else if (sType.isReader()) {
289         pipe((Reader)o, out, SerializerSession::handleThrown);
290      }
291      else if (sType.isInputStream()) {
292         pipe((InputStream)o, out, SerializerSession::handleThrown);
293      }
294      else
295         out.appendString(toString(o));
296
297      if (! isRecursion)
298         pop();
299      return out;
300   }
301
302   @SuppressWarnings({ "rawtypes", "unchecked" })
303   private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws SerializeException {
304
305      ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType();
306
307      m = sort(m);
308
309      // The map size may change as we're iterating over it, so
310      // grab a snapshot of the entries in a separate list.
311      List<SimpleMapEntry> entries = Utils.listOfSize(m.size());
312      m.forEach((k,v) -> entries.add(new SimpleMapEntry(k, v)));
313
314      out.startMap(entries.size());
315
316      entries.forEach(x -> {
317         Object value = x.value;
318         Object key = generalize(x.key, keyType);
319         serializeAnything(out, key, keyType, null, null);
320         serializeAnything(out, value, valueType, null, null);
321      });
322   }
323
324   private void serializeBeanMap(MsgPackOutputStream out, final BeanMap<?> m, String typeName) throws SerializeException {
325
326      Predicate<Object> checkNull = x -> isKeepNullProperties() || x != null;
327
328      List<BeanPropertyValue> values = new ArrayList<>();
329
330      if (typeName != null) {
331         BeanPropertyMeta pm = m.getMeta().getTypeProperty();
332         values.add(new BeanPropertyValue(pm, pm.getName(), typeName, null));
333      }
334
335      m.forEachValue(checkNull, (pMeta,key,value,thrown) -> {
336         if (thrown != null) {
337            onBeanGetterException(pMeta, thrown);
338            return;
339         }
340         BeanPropertyValue p = new BeanPropertyValue(pMeta, key, value, null);
341
342         if ((! isKeepNullProperties()) && willRecurse(p)) {
343            return; // Must handle the case where recursion occurs and property is not serialized.
344         }
345
346         values.add(p);
347      });
348
349      out.startMap(values.size());
350
351      values.forEach(x -> {
352         BeanPropertyMeta pMeta = x.getMeta();
353         if (pMeta.canRead()) {
354            ClassMeta<?> cMeta = x.getClassMeta();
355            String key = x.getName();
356            Object value = x.getValue();
357            serializeAnything(out, key, null, null, null);
358            serializeAnything(out, value, cMeta, key, pMeta);
359         }
360      });
361   }
362
363   private boolean willRecurse(BeanPropertyValue v) throws SerializeException {
364      ClassMeta<?> aType = push2(v.getName(), v.getValue(), v.getClassMeta());
365       if (aType != null)
366          pop();
367       return aType == null;
368   }
369
370   private static class SimpleMapEntry {
371      final Object key;
372      final Object value;
373
374      SimpleMapEntry(Object key, Object value) {
375         this.key = key;
376         this.value = value;
377      }
378   }
379
380   @SuppressWarnings({"rawtypes", "unchecked"})
381   private void serializeCollection(MsgPackOutputStream out, Collection c, ClassMeta<?> type) throws SerializeException {
382      ClassMeta<?> elementType = type.getElementType();
383      List<Object> l = Utils.listOfSize(c.size());
384      c = sort(c);
385      l.addAll(c);
386      out.startArray(l.size());
387      l.forEach(x -> serializeAnything(out, x, elementType, "<iterator>", null));
388   }
389
390   //-----------------------------------------------------------------------------------------------------------------
391   // Properties
392   //-----------------------------------------------------------------------------------------------------------------
393
394   @Override
395   protected boolean isAddBeanTypes() {
396      return ctx.isAddBeanTypes();
397   }
398}