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