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 java.io.IOException; 016import java.util.*; 017 018import org.apache.juneau.*; 019import org.apache.juneau.internal.*; 020import org.apache.juneau.serializer.*; 021import org.apache.juneau.transform.*; 022 023/** 024 * Session object that lives for the duration of a single use of {@link MsgPackSerializer}. 025 * 026 * <p> 027 * This class is NOT thread safe. 028 * It is typically discarded after one-time use although it can be reused within the same thread. 029 */ 030public final class MsgPackSerializerSession extends OutputStreamSerializerSession { 031 032 private final MsgPackSerializer ctx; 033 034 /** 035 * Create a new session using properties specified in the context. 036 * 037 * @param ctx 038 * The context creating this session object. 039 * The context contains all the configuration settings for this object. 040 * @param args 041 * Runtime arguments. 042 * These specify session-level information such as locale and URI context. 043 * It also include session-level properties that override the properties defined on the bean and 044 * serializer contexts. 045 */ 046 protected MsgPackSerializerSession(MsgPackSerializer ctx, SerializerSessionArgs args) { 047 super(ctx, args); 048 this.ctx = ctx; 049 } 050 051 @Override /* SerializerSession */ 052 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 053 serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null); 054 } 055 056 /* 057 * Converts the specified output target object to an {@link MsgPackOutputStream}. 058 */ 059 private static final MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws IOException { 060 Object output = out.getRawOutput(); 061 if (output instanceof MsgPackOutputStream) 062 return (MsgPackOutputStream)output; 063 MsgPackOutputStream os = new MsgPackOutputStream(out.getOutputStream()); 064 out.setOutputStream(os); 065 return os; 066 } 067 068 /* 069 * Workhorse method. 070 * Determines the type of object, and then calls the appropriate type-specific serialization method. 071 */ 072 @SuppressWarnings({ "rawtypes" }) 073 private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws IOException, SerializeException { 074 075 if (o == null) 076 return out.appendNull(); 077 078 if (eType == null) 079 eType = object(); 080 081 ClassMeta<?> aType; // The actual type 082 ClassMeta<?> sType; // The serialized type 083 084 aType = push2(attrName, o, eType); 085 boolean isRecursion = aType == null; 086 087 // Handle recursion 088 if (aType == null) { 089 o = null; 090 aType = object(); 091 } 092 093 // Handle Optional<X> 094 if (isOptional(aType)) { 095 o = getOptionalValue(o); 096 eType = getOptionalType(eType); 097 aType = getClassMetaForObject(o, object()); 098 } 099 100 sType = aType; 101 String typeName = getBeanTypeName(eType, aType, pMeta); 102 103 // Swap if necessary 104 PojoSwap swap = aType.getPojoSwap(this); 105 if (swap != null) { 106 o = swap(swap, o); 107 sType = swap.getSwapClassMeta(this); 108 109 // If the getSwapClass() method returns Object, we need to figure out 110 // the actual type now. 111 if (sType.isObject()) 112 sType = getClassMetaForObject(o); 113 } 114 115 // '\0' characters are considered null. 116 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) 117 out.appendNull(); 118 else if (sType.isBoolean()) 119 out.appendBoolean((Boolean)o); 120 else if (sType.isNumber()) 121 out.appendNumber((Number)o); 122 else if (sType.isBean()) 123 serializeBeanMap(out, toBeanMap(o), typeName); 124 else if (sType.isUri() || (pMeta != null && pMeta.isUri())) 125 out.appendString(resolveUri(o.toString())); 126 else if (sType.isMap()) { 127 if (o instanceof BeanMap) 128 serializeBeanMap(out, (BeanMap)o, typeName); 129 else 130 serializeMap(out, (Map)o, eType); 131 } 132 else if (sType.isCollection()) { 133 serializeCollection(out, (Collection) o, eType); 134 } 135 else if (sType.isArray()) { 136 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 137 } 138 else if (sType.isReader() || sType.isInputStream()) { 139 IOUtils.pipe(o, out); 140 } 141 else 142 out.appendString(toString(o)); 143 144 if (! isRecursion) 145 pop(); 146 return out; 147 } 148 149 @SuppressWarnings({ "rawtypes", "unchecked" }) 150 private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws IOException, SerializeException { 151 152 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 153 154 m = sort(m); 155 156 // The map size may change as we're iterating over it, so 157 // grab a snapshot of the entries in a separate list. 158 List<SimpleMapEntry> entries = new ArrayList<>(m.size()); 159 for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) 160 entries.add(new SimpleMapEntry(e.getKey(), e.getValue())); 161 162 out.startMap(entries.size()); 163 164 for (SimpleMapEntry e : entries) { 165 Object value = e.value; 166 Object key = generalize(e.key, keyType); 167 168 serializeAnything(out, key, keyType, null, null); 169 serializeAnything(out, value, valueType, null, null); 170 } 171 } 172 173 private void serializeBeanMap(MsgPackOutputStream out, final BeanMap<?> m, String typeName) throws IOException, SerializeException { 174 175 List<BeanPropertyValue> values = m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null); 176 177 int size = values.size(); 178 for (BeanPropertyValue p : values) 179 if (p.getThrown() != null) 180 size--; 181 out.startMap(size); 182 183 for (BeanPropertyValue p : values) { 184 BeanPropertyMeta pMeta = p.getMeta(); 185 if (pMeta.canRead()) { 186 ClassMeta<?> cMeta = p.getClassMeta(); 187 String key = p.getName(); 188 Object value = p.getValue(); 189 Throwable t = p.getThrown(); 190 if (t != null) 191 onBeanGetterException(pMeta, t); 192 else { 193 serializeAnything(out, key, null, null, null); 194 serializeAnything(out, value, cMeta, key, pMeta); 195 } 196 } 197 } 198 } 199 200 private static final class SimpleMapEntry { 201 final Object key; 202 final Object value; 203 204 SimpleMapEntry(Object key, Object value) { 205 this.key = key; 206 this.value = value; 207 } 208 } 209 210 @SuppressWarnings({"rawtypes", "unchecked"}) 211 private void serializeCollection(MsgPackOutputStream out, Collection c, ClassMeta<?> type) throws IOException, SerializeException { 212 213 ClassMeta<?> elementType = type.getElementType(); 214 List<Object> l = new ArrayList<>(c.size()); 215 216 c = sort(c); 217 l.addAll(c); 218 219 out.startArray(l.size()); 220 221 for (Object o : l) 222 serializeAnything(out, o, elementType, "<iterator>", null); 223 } 224 225 //----------------------------------------------------------------------------------------------------------------- 226 // Properties 227 //----------------------------------------------------------------------------------------------------------------- 228 229 @Override 230 protected final boolean isAddBeanTypes() { 231 return ctx.isAddBeanTypes(); 232 } 233 234 //----------------------------------------------------------------------------------------------------------------- 235 // Other methods 236 //----------------------------------------------------------------------------------------------------------------- 237 238 @Override /* Session */ 239 public ObjectMap toMap() { 240 return super.toMap() 241 .append("MsgPackSerializerSession", new DefaultFilteringObjectMap() 242 ); 243 } 244}