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 sType = aType; 109 String typeName = getBeanTypeName(eType, aType, pMeta); 110 111 // Swap if necessary 112 PojoSwap swap = aType.getPojoSwap(this); 113 if (swap != null) { 114 o = swap(swap, o); 115 sType = swap.getSwapClassMeta(this); 116 117 // If the getSwapClass() method returns Object, we need to figure out 118 // the actual type now. 119 if (sType.isObject()) 120 sType = getClassMetaForObject(o); 121 } 122 123 String wrapperAttr = sType.getExtendedMeta(JsonClassMeta.class).getWrapperAttr(); 124 if (wrapperAttr != null) { 125 out.append('{').cr(indent).attr(wrapperAttr).append(':').s(indent); 126 indent++; 127 } 128 129 // '\0' characters are considered null. 130 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) { 131 out.append("null"); 132 } else if (sType.isNumber() || sType.isBoolean()) { 133 out.append(o); 134 } else if (sType.isBean()) { 135 serializeBeanMap(out, toBeanMap(o), typeName); 136 } else if (sType.isUri() || (pMeta != null && pMeta.isUri())) { 137 out.uriValue(o); 138 } else if (sType.isMap()) { 139 if (o instanceof BeanMap) 140 serializeBeanMap(out, (BeanMap)o, typeName); 141 else 142 serializeMap(out, (Map)o, eType); 143 } else if (sType.isCollection()) { 144 serializeCollection(out, (Collection) o, eType); 145 } else if (sType.isArray()) { 146 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 147 } else if (sType.isReader() || sType.isInputStream()) { 148 IOUtils.pipe(o, out); 149 } else { 150 out.stringValue(toString(o)); 151 } 152 153 if (wrapperAttr != null) { 154 indent--; 155 out.cre(indent-1).append('}'); 156 } 157 158 if (! isRecursion) 159 pop(); 160 return out; 161 } 162 163 @SuppressWarnings({ "rawtypes", "unchecked" }) 164 private SerializerWriter serializeMap(JsonWriter out, Map m, ClassMeta<?> type) throws IOException, SerializeException { 165 166 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 167 168 m = sort(m); 169 170 int i = indent; 171 out.append('{'); 172 173 Iterator mapEntries = m.entrySet().iterator(); 174 175 while (mapEntries.hasNext()) { 176 Map.Entry e = (Map.Entry) mapEntries.next(); 177 Object value = e.getValue(); 178 179 Object key = generalize(e.getKey(), keyType); 180 181 out.cr(i).attr(toString(key)).append(':').s(i); 182 183 serializeAnything(out, value, valueType, (key == null ? null : toString(key)), null); 184 185 if (mapEntries.hasNext()) 186 out.append(',').smi(i); 187 } 188 189 out.cre(i-1).append('}'); 190 191 return out; 192 } 193 194 private SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> m, String typeName) throws IOException, SerializeException { 195 int i = indent; 196 out.append('{'); 197 198 boolean addComma = false; 199 for (BeanPropertyValue p : m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) { 200 BeanPropertyMeta pMeta = p.getMeta(); 201 if (pMeta.canRead()) { 202 ClassMeta<?> cMeta = p.getClassMeta(); 203 String key = p.getName(); 204 Object value = p.getValue(); 205 Throwable t = p.getThrown(); 206 if (t != null) 207 onBeanGetterException(pMeta, t); 208 209 if (canIgnoreValue(cMeta, key, value)) 210 continue; 211 212 if (addComma) 213 out.append(',').smi(i); 214 215 out.cr(i).attr(key).append(':').s(i); 216 217 serializeAnything(out, value, cMeta, key, pMeta); 218 219 addComma = true; 220 } 221 } 222 out.cre(i-1).append('}'); 223 return out; 224 } 225 226 @SuppressWarnings({"rawtypes", "unchecked"}) 227 private SerializerWriter serializeCollection(JsonWriter out, Collection c, ClassMeta<?> type) throws IOException, SerializeException { 228 229 ClassMeta<?> elementType = type.getElementType(); 230 231 c = sort(c); 232 233 out.append('['); 234 235 for (Iterator i = c.iterator(); i.hasNext();) { 236 Object value = i.next(); 237 out.cr(indent); 238 serializeAnything(out, value, elementType, "<iterator>", null); 239 if (i.hasNext()) 240 out.append(',').smi(indent); 241 } 242 out.cre(indent-1).append(']'); 243 return out; 244 } 245 246 /** 247 * Converts the specified output target object to an {@link JsonWriter}. 248 * 249 * @param out The output target object. 250 * @return The output target object wrapped in an {@link JsonWriter}. 251 * @throws IOException Thrown by underlying stream. 252 */ 253 protected final JsonWriter getJsonWriter(SerializerPipe out) throws IOException { 254 Object output = out.getRawOutput(); 255 if (output instanceof JsonWriter) 256 return (JsonWriter)output; 257 JsonWriter w = new JsonWriter(out.getWriter(), isUseWhitespace(), getMaxIndent(), isEscapeSolidus(), getQuoteChar(), 258 isSimpleMode(), isTrimStrings(), getUriResolver()); 259 out.setWriter(w); 260 return w; 261 } 262 263 //----------------------------------------------------------------------------------------------------------------- 264 // Properties 265 //----------------------------------------------------------------------------------------------------------------- 266 267 /** 268 * Configuration property: Add <js>"_type"</js> properties when needed. 269 * 270 * @see JsonSerializer#JSON_addBeanTypes 271 * @return 272 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 273 * through reflection. 274 */ 275 @Override 276 protected final boolean isAddBeanTypes() { 277 return ctx.isAddBeanTypes(); 278 } 279 280 /** 281 * Configuration property: Prefix solidus <js>'/'</js> characters with escapes. 282 * 283 * @see JsonSerializer#JSON_escapeSolidus 284 * @return 285 * <jk>true</jk> if solidus (e.g. slash) characters should be escaped. 286 */ 287 protected final boolean isEscapeSolidus() { 288 return ctx.isEscapeSolidus(); 289 } 290 291 /** 292 * Configuration property: Simple JSON mode. 293 * 294 * @see JsonSerializer#JSON_simpleMode 295 * @return 296 * <jk>true</jk> if JSON attribute names will only be quoted when necessary. 297 * <br>Otherwise, they are always quoted. 298 */ 299 protected final boolean isSimpleMode() { 300 return ctx.isSimpleMode(); 301 } 302 303 //----------------------------------------------------------------------------------------------------------------- 304 // Other methods 305 //----------------------------------------------------------------------------------------------------------------- 306 307 @Override /* Session */ 308 public ObjectMap toMap() { 309 return super.toMap() 310 .append("JsonSerializerSession", new DefaultFilteringObjectMap() 311 ); 312 } 313}