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.uon; 014 015import java.io.*; 016import java.util.*; 017 018import org.apache.juneau.*; 019import org.apache.juneau.httppart.*; 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 UonSerializer}. 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 UonSerializerSession extends WriterSerializerSession implements HttpPartSerializerSession { 032 033 private final UonSerializer ctx; 034 private final boolean plainTextParams; 035 036 /** 037 * @param ctx 038 * The context creating this session object. 039 * The context contains all the configuration settings for this object. 040 * @param encode Override the {@link UonSerializer#UON_encoding} setting. 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 public UonSerializerSession(UonSerializer ctx, Boolean encode, SerializerSessionArgs args) { 048 super(ctx, args); 049 this.ctx = ctx; 050 plainTextParams = ctx.getParamFormat() == ParamFormat.PLAINTEXT; 051 } 052 053 /** 054 * Converts the specified output target object to an {@link UonWriter}. 055 * 056 * @param out The output target object. 057 * @return The output target object wrapped in an {@link UonWriter}. 058 * @throws IOException Thrown by underlying stream. 059 */ 060 protected final UonWriter getUonWriter(SerializerPipe out) throws IOException { 061 Object output = out.getRawOutput(); 062 if (output instanceof UonWriter) 063 return (UonWriter)output; 064 UonWriter w = new UonWriter(this, out.getWriter(), isUseWhitespace(), getMaxIndent(), isEncoding(), isTrimStrings(), plainTextParams, getUriResolver()); 065 out.setWriter(w); 066 return w; 067 } 068 069 private final UonWriter getUonWriter(Writer out) throws Exception { 070 return new UonWriter(this, out, isUseWhitespace(), getMaxIndent(), isEncoding(), isTrimStrings(), plainTextParams, getUriResolver()); 071 } 072 073 @Override /* Serializer */ 074 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 075 serializeAnything(getUonWriter(out), o, getExpectedRootType(o), "root", null); 076 } 077 078 /** 079 * Workhorse method. 080 * 081 * <p> 082 * Determines the type of object, and then calls the appropriate type-specific serialization method. 083 * 084 * @param out The writer to serialize to. 085 * @param o The object being serialized. 086 * @param eType The expected type of the object if this is a bean property. 087 * @param attrName 088 * The bean property name if this is a bean property. 089 * <jk>null</jk> if this isn't a bean property being serialized. 090 * @param pMeta The bean property metadata. 091 * @return The same writer passed in. 092 * @throws IOException Thrown by underlying stream. 093 * @throws SerializeException Generic serialization error occurred. 094 */ 095 @SuppressWarnings({ "rawtypes" }) 096 protected SerializerWriter serializeAnything(UonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws IOException, SerializeException { 097 098 if (o == null) { 099 out.appendObject(null, false); 100 return out; 101 } 102 103 if (eType == null) 104 eType = object(); 105 106 ClassMeta<?> aType; // The actual type 107 ClassMeta<?> sType; // The serialized type 108 109 aType = push2(attrName, o, eType); 110 boolean isRecursion = aType == null; 111 112 // Handle recursion 113 if (aType == null) { 114 o = null; 115 aType = object(); 116 } 117 118 // Handle Optional<X> 119 if (isOptional(aType)) { 120 o = getOptionalValue(o); 121 eType = getOptionalType(eType); 122 aType = getClassMetaForObject(o, object()); 123 } 124 125 sType = aType; 126 String typeName = getBeanTypeName(eType, aType, pMeta); 127 128 // Swap if necessary 129 PojoSwap swap = aType.getPojoSwap(this); 130 if (swap != null) { 131 o = swap(swap, o); 132 sType = swap.getSwapClassMeta(this); 133 134 // If the getSwapClass() method returns Object, we need to figure out 135 // the actual type now. 136 if (sType.isObject()) 137 sType = getClassMetaForObject(o); 138 } 139 140 // '\0' characters are considered null. 141 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) 142 out.appendObject(null, false); 143 else if (sType.isBoolean()) 144 out.appendBoolean(o); 145 else if (sType.isNumber()) 146 out.appendNumber(o); 147 else if (sType.isBean()) 148 serializeBeanMap(out, toBeanMap(o), typeName); 149 else if (sType.isUri() || (pMeta != null && pMeta.isUri())) 150 out.appendUri(o); 151 else if (sType.isMap()) { 152 if (o instanceof BeanMap) 153 serializeBeanMap(out, (BeanMap)o, typeName); 154 else 155 serializeMap(out, (Map)o, eType); 156 } 157 else if (sType.isCollection()) { 158 serializeCollection(out, (Collection) o, eType); 159 } 160 else if (sType.isArray()) { 161 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 162 } 163 else if (sType.isReader() || sType.isInputStream()) { 164 IOUtils.pipe(o, out); 165 } 166 else { 167 out.appendObject(o, false); 168 } 169 170 if (! isRecursion) 171 pop(); 172 return out; 173 } 174 175 @SuppressWarnings({ "rawtypes", "unchecked" }) 176 private SerializerWriter serializeMap(UonWriter out, Map m, ClassMeta<?> type) throws IOException, SerializeException { 177 178 m = sort(m); 179 180 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 181 182 if (! plainTextParams) 183 out.append('('); 184 185 Iterator mapEntries = m.entrySet().iterator(); 186 187 while (mapEntries.hasNext()) { 188 Map.Entry e = (Map.Entry) mapEntries.next(); 189 Object value = e.getValue(); 190 Object key = generalize(e.getKey(), keyType); 191 out.cr(indent).appendObject(key, false).append('='); 192 serializeAnything(out, value, valueType, toString(key), null); 193 if (mapEntries.hasNext()) 194 out.append(','); 195 } 196 197 if (m.size() > 0) 198 out.cre(indent-1); 199 200 if (! plainTextParams) 201 out.append(')'); 202 203 return out; 204 } 205 206 private SerializerWriter serializeBeanMap(UonWriter out, BeanMap<?> m, String typeName) throws IOException, SerializeException { 207 208 if (! plainTextParams) 209 out.append('('); 210 211 boolean addComma = false; 212 213 for (BeanPropertyValue p : m.getValues(isTrimNullProperties(), typeName != null ? createBeanTypeNameProperty(m, typeName) : null)) { 214 BeanPropertyMeta pMeta = p.getMeta(); 215 if (pMeta.canRead()) { 216 ClassMeta<?> cMeta = p.getClassMeta(); 217 218 String key = p.getName(); 219 Object value = p.getValue(); 220 Throwable t = p.getThrown(); 221 if (t != null) 222 onBeanGetterException(pMeta, t); 223 224 if (canIgnoreValue(cMeta, key, value)) 225 continue; 226 227 if (addComma) 228 out.append(','); 229 230 out.cr(indent).appendObject(key, false).append('='); 231 232 serializeAnything(out, value, cMeta, key, pMeta); 233 234 addComma = true; 235 } 236 } 237 238 if (m.size() > 0) 239 out.cre(indent-1); 240 if (! plainTextParams) 241 out.append(')'); 242 243 return out; 244 } 245 246 @SuppressWarnings({ "rawtypes", "unchecked" }) 247 private SerializerWriter serializeCollection(UonWriter out, Collection c, ClassMeta<?> type) throws IOException, SerializeException { 248 249 ClassMeta<?> elementType = type.getElementType(); 250 251 c = sort(c); 252 253 if (! plainTextParams) 254 out.append('@').append('('); 255 256 for (Iterator i = c.iterator(); i.hasNext();) { 257 out.cr(indent); 258 serializeAnything(out, i.next(), elementType, "<iterator>", null); 259 if (i.hasNext()) 260 out.append(','); 261 } 262 263 if (c.size() > 0) 264 out.cre(indent-1); 265 if (! plainTextParams) 266 out.append(')'); 267 268 return out; 269 } 270 271 @Override /* HttpPartSerializer */ 272 public String serialize(HttpPartType type, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException { 273 try { 274 // Shortcut for simple types. 275 ClassMeta<?> cm = getClassMetaForObject(value); 276 if (cm != null) { 277 if (cm.isNumber() || cm.isBoolean()) 278 return ClassUtils.toString(value); 279 if (cm.isString()) { 280 String s = ClassUtils.toString(value); 281 if (s.isEmpty() || ! UonUtils.needsQuotes(s)) 282 return s; 283 } 284 } 285 StringWriter w = new StringWriter(); 286 serializeAnything(getUonWriter(w), value, getExpectedRootType(value), "root", null); 287 return w.toString(); 288 } catch (Exception e) { 289 throw new RuntimeException(e); 290 } 291 } 292 293 @Override /* HttpPartSerializer */ 294 public String serialize(HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException { 295 return serialize(null, schema, value); 296 } 297 298 //----------------------------------------------------------------------------------------------------------------- 299 // Properties 300 //----------------------------------------------------------------------------------------------------------------- 301 302 /** 303 * Configuration property: Add <js>"_type"</js> properties when needed. 304 * 305 * @see UonSerializer#UON_addBeanTypes 306 * @return 307 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 308 * through reflection. 309 */ 310 @Override 311 protected final boolean isAddBeanTypes() { 312 return ctx.isAddBeanTypes(); 313 } 314 315 /** 316 * Configuration property: Encode non-valid URI characters. 317 * 318 * @see UonSerializer#UON_encoding 319 * @return 320 * <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs. 321 */ 322 protected final boolean isEncoding() { 323 return ctx.isEncoding(); 324 } 325 326 /** 327 * Configuration property: Format to use for query/form-data/header values. 328 * 329 * @see UonSerializer#UON_paramFormat 330 * @return 331 * Specifies the format to use for URL GET parameter keys and values. 332 */ 333 protected final ParamFormat getParamFormat() { 334 return ctx.getParamFormat(); 335 } 336 337 //----------------------------------------------------------------------------------------------------------------- 338 // Other methods 339 //----------------------------------------------------------------------------------------------------------------- 340 341 @Override /* Session */ 342 public ObjectMap toMap() { 343 return super.toMap() 344 .append("UonSerializerSession", new DefaultFilteringObjectMap() 345 ); 346 } 347}