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