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.msgpack.MsgPackSerializer.*; 016 017import java.util.*; 018 019import org.apache.juneau.*; 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 boolean 034 addBeanTypeProperties; 035 036 /** 037 * Create a new session using properties specified in the context. 038 * 039 * @param ctx 040 * The context creating this session object. 041 * The context contains all the configuration settings for this object. 042 * @param args 043 * Runtime arguments. 044 * These specify session-level information such as locale and URI context. 045 * It also include session-level properties that override the properties defined on the bean and 046 * serializer contexts. 047 */ 048 protected MsgPackSerializerSession(MsgPackSerializer ctx, SerializerSessionArgs args) { 049 super(ctx, args); 050 addBeanTypeProperties = getProperty(MSGPACK_addBeanTypeProperties, boolean.class, ctx.addBeanTypeProperties); 051 } 052 053 @Override /* Session */ 054 public ObjectMap asMap() { 055 return super.asMap() 056 .append("MsgPackSerializerSession", new ObjectMap() 057 .append("addBeanTypeProperties", addBeanTypeProperties) 058 ); 059 } 060 061 /** 062 * Returns the {@link MsgPackSerializer#MSGPACK_addBeanTypeProperties} setting value for this session. 063 * 064 * @return The {@link MsgPackSerializer#MSGPACK_addBeanTypeProperties} setting value for this session. 065 */ 066 @Override /* SerializerSession */ 067 protected final boolean isAddBeanTypeProperties() { 068 return addBeanTypeProperties; 069 } 070 071 @Override /* SerializerSession */ 072 protected void doSerialize(SerializerPipe out, Object o) throws Exception { 073 serializeAnything(getMsgPackOutputStream(out), o, getExpectedRootType(o), "root", null); 074 } 075 076 /* 077 * Converts the specified output target object to an {@link MsgPackOutputStream}. 078 */ 079 private static final MsgPackOutputStream getMsgPackOutputStream(SerializerPipe out) throws Exception { 080 Object output = out.getRawOutput(); 081 if (output instanceof MsgPackOutputStream) 082 return (MsgPackOutputStream)output; 083 MsgPackOutputStream os = new MsgPackOutputStream(out.getOutputStream()); 084 out.setOutputStream(os); 085 return os; 086 } 087 088 /* 089 * Workhorse method. 090 * Determines the type of object, and then calls the appropriate type-specific serialization method. 091 */ 092 @SuppressWarnings({ "rawtypes", "unchecked" }) 093 private MsgPackOutputStream serializeAnything(MsgPackOutputStream out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws Exception { 094 095 if (o == null) 096 return out.appendNull(); 097 098 if (eType == null) 099 eType = object(); 100 101 ClassMeta<?> aType; // The actual type 102 ClassMeta<?> sType; // The serialized type 103 104 aType = push(attrName, o, eType); 105 boolean isRecursion = aType == null; 106 107 // Handle recursion 108 if (aType == null) { 109 o = null; 110 aType = object(); 111 } 112 113 sType = aType; 114 String typeName = getBeanTypeName(eType, aType, pMeta); 115 116 // Swap if necessary 117 PojoSwap swap = aType.getPojoSwap(this); 118 if (swap != null) { 119 o = swap.swap(this, o); 120 sType = swap.getSwapClassMeta(this); 121 122 // If the getSwapClass() method returns Object, we need to figure out 123 // the actual type now. 124 if (sType.isObject()) 125 sType = getClassMetaForObject(o); 126 } 127 128 // '\0' characters are considered null. 129 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) 130 out.appendNull(); 131 else if (sType.isBoolean()) 132 out.appendBoolean((Boolean)o); 133 else if (sType.isNumber()) 134 out.appendNumber((Number)o); 135 else if (sType.isBean()) 136 serializeBeanMap(out, toBeanMap(o), typeName); 137 else if (sType.isUri() || (pMeta != null && pMeta.isUri())) 138 out.appendString(resolveUri(o.toString())); 139 else if (sType.isMap()) { 140 if (o instanceof BeanMap) 141 serializeBeanMap(out, (BeanMap)o, typeName); 142 else 143 serializeMap(out, (Map)o, eType); 144 } 145 else if (sType.isCollection()) { 146 serializeCollection(out, (Collection) o, eType); 147 } 148 else if (sType.isArray()) { 149 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 150 } 151 else if (sType.isReader() || sType.isInputStream()) { 152 IOUtils.pipe(o, out); 153 } 154 else 155 out.appendString(toString(o)); 156 157 if (! isRecursion) 158 pop(); 159 return out; 160 } 161 162 @SuppressWarnings({ "rawtypes", "unchecked" }) 163 private void serializeMap(MsgPackOutputStream out, Map m, ClassMeta<?> type) throws Exception { 164 165 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 166 167 m = sort(m); 168 169 // The map size may change as we're iterating over it, so 170 // grab a snapshot of the entries in a separate list. 171 List<SimpleMapEntry> entries = new ArrayList<>(m.size()); 172 for (Map.Entry e : (Set<Map.Entry>)m.entrySet()) 173 entries.add(new SimpleMapEntry(e.getKey(), e.getValue())); 174 175 out.startMap(entries.size()); 176 177 for (SimpleMapEntry e : entries) { 178 Object value = e.value; 179 Object key = generalize(e.key, keyType); 180 181 serializeAnything(out, key, keyType, null, null); 182 serializeAnything(out, value, valueType, null, null); 183 } 184 } 185 186 private void serializeBeanMap(MsgPackOutputStream out, final BeanMap<?> m, String typeName) throws Exception { 187 188 List<BeanPropertyValue> values = m.getValues(isTrimNulls(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null); 189 190 int size = values.size(); 191 for (BeanPropertyValue p : values) 192 if (p.getThrown() != null) 193 size--; 194 out.startMap(size); 195 196 for (BeanPropertyValue p : values) { 197 BeanPropertyMeta pMeta = p.getMeta(); 198 if (pMeta.canRead()) { 199 ClassMeta<?> cMeta = p.getClassMeta(); 200 String key = p.getName(); 201 Object value = p.getValue(); 202 Throwable t = p.getThrown(); 203 if (t != null) 204 onBeanGetterException(pMeta, t); 205 else { 206 serializeAnything(out, key, null, null, null); 207 serializeAnything(out, value, cMeta, key, pMeta); 208 } 209 } 210 } 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 Exception { 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}