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.json; 014 015import java.io.*; 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 JsonSerializer}. 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 class JsonSerializerSession extends WriterSerializerSession { 031 032 private final JsonSerializer 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 JsonSerializerSession(JsonSerializer ctx, SerializerSessionArgs args) { 047 super(ctx, args); 048 this.ctx = ctx; 049 } 050 051 @Override /* SerializerSesssion */ 052 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 053 serializeAnything(getJsonWriter(out), o, getExpectedRootType(o), "root", null); 054 } 055 056 /** 057 * Method that can be called from subclasses to serialize an object to JSON. 058 * 059 * <p> 060 * Used by {@link JsonSchemaSerializerSession} for serializing examples to JSON. 061 * 062 * @param o The object to serialize. 063 * @return The serialized object. 064 * @throws Exception Error occurred. 065 */ 066 protected String serializeJson(Object o) throws Exception { 067 StringWriter sw = new StringWriter(); 068 serializeAnything(getJsonWriter(createPipe(sw)), o, getExpectedRootType(o), "root", null); 069 return sw.toString(); 070 } 071 072 /** 073 * Workhorse method. 074 * Determines the type of object, and then calls the appropriate type-specific serialization method. 075 * 076 * @param out The output writer. 077 * @param o The object to serialize. 078 * @param eType The expected type. 079 * @param attrName The attribute name. 080 * @param pMeta The bean property currently being parsed. 081 * @return The same writer passed in. 082 * @throws IOException Thrown by underlying stream. 083 * @throws SerializeException General serialization error occurred. 084 */ 085 @SuppressWarnings({ "rawtypes" }) 086 protected JsonWriter serializeAnything(JsonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws IOException, SerializeException { 087 088 if (o == null) { 089 out.append("null"); 090 return out; 091 } 092 093 if (eType == null) 094 eType = object(); 095 096 ClassMeta<?> aType; // The actual type 097 ClassMeta<?> sType; // The serialized type 098 099 aType = push2(attrName, o, eType); 100 boolean isRecursion = aType == null; 101 102 // Handle recursion 103 if (aType == null) { 104 o = null; 105 aType = object(); 106 } 107 108 // Handle Optional<X> 109 if (isOptional(aType)) { 110 o = getOptionalValue(o); 111 eType = getOptionalType(eType); 112 aType = getClassMetaForObject(o, object()); 113 } 114 115 sType = aType; 116 String typeName = getBeanTypeName(eType, aType, pMeta); 117 118 // Swap if necessary 119 PojoSwap swap = aType.getPojoSwap(this); 120 if (swap != null) { 121 o = swap(swap, o); 122 sType = swap.getSwapClassMeta(this); 123 124 // If the getSwapClass() method returns Object, we need to figure out 125 // the actual type now. 126 if (sType.isObject()) 127 sType = getClassMetaForObject(o); 128 } 129 130 String wrapperAttr = getJsonClassMeta(sType).getWrapperAttr(); 131 if (wrapperAttr != null) { 132 out.append('{').cr(indent).attr(wrapperAttr).append(':').s(indent); 133 indent++; 134 } 135 136 // '\0' characters are considered null. 137 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) { 138 out.append("null"); 139 } else if (sType.isNumber() || sType.isBoolean()) { 140 out.append(o); 141 } else if (sType.isBean()) { 142 serializeBeanMap(out, toBeanMap(o), typeName); 143 } else if (sType.isUri() || (pMeta != null && pMeta.isUri())) { 144 out.uriValue(o); 145 } else if (sType.isMap()) { 146 if (o instanceof BeanMap) 147 serializeBeanMap(out, (BeanMap)o, typeName); 148 else 149 serializeMap(out, (Map)o, eType); 150 } else if (sType.isCollection()) { 151 serializeCollection(out, (Collection) o, eType); 152 } else if (sType.isArray()) { 153 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 154 } else if (sType.isReader() || sType.isInputStream()) { 155 IOUtils.pipe(o, out); 156 } else { 157 out.stringValue(toString(o)); 158 } 159 160 if (wrapperAttr != null) { 161 indent--; 162 out.cre(indent-1).append('}'); 163 } 164 165 if (! isRecursion) 166 pop(); 167 return out; 168 } 169 170 @SuppressWarnings({ "rawtypes", "unchecked" }) 171 private SerializerWriter serializeMap(JsonWriter out, Map m, ClassMeta<?> type) throws IOException, SerializeException { 172 173 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 174 175 m = sort(m); 176 177 int i = indent; 178 out.append('{'); 179 180 Iterator mapEntries = m.entrySet().iterator(); 181 182 while (mapEntries.hasNext()) { 183 Map.Entry e = (Map.Entry) mapEntries.next(); 184 Object value = e.getValue(); 185 186 Object key = generalize(e.getKey(), keyType); 187 188 out.cr(i).attr(toString(key)).append(':').s(i); 189 190 serializeAnything(out, value, valueType, (key == null ? null : toString(key)), null); 191 192 if (mapEntries.hasNext()) 193 out.append(',').smi(i); 194 } 195 196 out.cre(i-1).append('}'); 197 198 return out; 199 } 200 201 private SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> m, String typeName) throws IOException, SerializeException { 202 int i = indent; 203 out.append('{'); 204 205 boolean addComma = false; 206 for (BeanPropertyValue p : m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) { 207 BeanPropertyMeta pMeta = p.getMeta(); 208 if (pMeta.canRead()) { 209 ClassMeta<?> cMeta = p.getClassMeta(); 210 String key = p.getName(); 211 Object value = p.getValue(); 212 Throwable t = p.getThrown(); 213 if (t != null) 214 onBeanGetterException(pMeta, t); 215 216 if (canIgnoreValue(cMeta, key, value)) 217 continue; 218 219 if (addComma) 220 out.append(',').smi(i); 221 222 out.cr(i).attr(key).append(':').s(i); 223 224 serializeAnything(out, value, cMeta, key, pMeta); 225 226 addComma = true; 227 } 228 } 229 out.cre(i-1).append('}'); 230 return out; 231 } 232 233 @SuppressWarnings({"rawtypes", "unchecked"}) 234 private SerializerWriter serializeCollection(JsonWriter out, Collection c, ClassMeta<?> type) throws IOException, SerializeException { 235 236 ClassMeta<?> elementType = type.getElementType(); 237 238 c = sort(c); 239 240 out.append('['); 241 242 for (Iterator i = c.iterator(); i.hasNext();) { 243 Object value = i.next(); 244 out.cr(indent); 245 serializeAnything(out, value, elementType, "<iterator>", null); 246 if (i.hasNext()) 247 out.append(',').smi(indent); 248 } 249 out.cre(indent-1).append(']'); 250 return out; 251 } 252 253 /** 254 * Converts the specified output target object to an {@link JsonWriter}. 255 * 256 * @param out The output target object. 257 * @return The output target object wrapped in an {@link JsonWriter}. 258 * @throws IOException Thrown by underlying stream. 259 */ 260 protected final JsonWriter getJsonWriter(SerializerPipe out) throws IOException { 261 Object output = out.getRawOutput(); 262 if (output instanceof JsonWriter) 263 return (JsonWriter)output; 264 JsonWriter w = new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isEscapeSolidus(), getQuoteChar(), 265 isSimpleMode(), isTrimStrings(), getUriResolver()); 266 out.setWriter(w); 267 return w; 268 } 269 270 //----------------------------------------------------------------------------------------------------------------- 271 // Properties 272 //----------------------------------------------------------------------------------------------------------------- 273 274 /** 275 * Configuration property: Add <js>"_type"</js> properties when needed. 276 * 277 * @see JsonSerializer#JSON_addBeanTypes 278 * @return 279 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 280 * through reflection. 281 */ 282 @Override 283 protected final boolean isAddBeanTypes() { 284 return ctx.isAddBeanTypes(); 285 } 286 287 /** 288 * Configuration property: Prefix solidus <js>'/'</js> characters with escapes. 289 * 290 * @see JsonSerializer#JSON_escapeSolidus 291 * @return 292 * <jk>true</jk> if solidus (e.g. slash) characters should be escaped. 293 */ 294 protected final boolean isEscapeSolidus() { 295 return ctx.isEscapeSolidus(); 296 } 297 298 /** 299 * Configuration property: Simple JSON mode. 300 * 301 * @see JsonSerializer#JSON_simpleMode 302 * @return 303 * <jk>true</jk> if JSON attribute names will only be quoted when necessary. 304 * <br>Otherwise, they are always quoted. 305 */ 306 protected final boolean isSimpleMode() { 307 return ctx.isSimpleMode(); 308 } 309 310 //----------------------------------------------------------------------------------------------------------------- 311 // Extended metadata 312 //----------------------------------------------------------------------------------------------------------------- 313 314 /** 315 * Returns the language-specific metadata on the specified class. 316 * 317 * @param cm The class to return the metadata on. 318 * @return The metadata. 319 */ 320 protected JsonClassMeta getJsonClassMeta(ClassMeta<?> cm) { 321 return ctx.getJsonClassMeta(cm); 322 } 323 324 //----------------------------------------------------------------------------------------------------------------- 325 // Other methods 326 //----------------------------------------------------------------------------------------------------------------- 327 328 @Override /* Session */ 329 public ObjectMap toMap() { 330 return super.toMap() 331 .append("JsonSerializerSession", new DefaultFilteringObjectMap() 332 ); 333 } 334}