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