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