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