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}