001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.uon; 018 019import static org.apache.juneau.common.utils.IOUtils.*; 020import static org.apache.juneau.common.utils.ThrowableUtils.*; 021 022import java.io.*; 023import java.lang.reflect.*; 024import java.nio.charset.*; 025import java.util.*; 026import java.util.function.*; 027 028import org.apache.juneau.*; 029import org.apache.juneau.httppart.*; 030import org.apache.juneau.internal.*; 031import org.apache.juneau.reflect.*; 032import org.apache.juneau.serializer.*; 033import org.apache.juneau.svl.*; 034import org.apache.juneau.swap.*; 035 036/** 037 * Session object that lives for the duration of a single use of {@link UonSerializer}. 038 * 039 * <h5 class='section'>Notes:</h5><ul> 040 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 041 * </ul> 042 * 043 * <h5 class='section'>See Also:</h5><ul> 044 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/UonBasics">UON Basics</a> 045 046 * </ul> 047 */ 048public class UonSerializerSession extends WriterSerializerSession implements HttpPartSerializerSession { 049 050 //----------------------------------------------------------------------------------------------------------------- 051 // Static 052 //----------------------------------------------------------------------------------------------------------------- 053 054 /** 055 * Creates a new builder for this object. 056 * 057 * @param ctx The context creating this session. 058 * @return A new builder. 059 */ 060 public static Builder create(UonSerializer ctx) { 061 return new Builder(ctx); 062 } 063 064 //----------------------------------------------------------------------------------------------------------------- 065 // Builder 066 //----------------------------------------------------------------------------------------------------------------- 067 068 /** 069 * Builder class. 070 */ 071 public static class Builder extends WriterSerializerSession.Builder { 072 073 UonSerializer ctx; 074 boolean encoding; 075 076 /** 077 * Constructor 078 * 079 * @param ctx The context creating this session. 080 */ 081 protected Builder(UonSerializer ctx) { 082 super(ctx); 083 this.ctx = ctx; 084 encoding = ctx.encoding; 085 } 086 087 @Override 088 public UonSerializerSession build() { 089 return new UonSerializerSession(this); 090 } 091 092 /** 093 * Overrides the encoding setting for this session. 094 * 095 * @param value The new value for this setting. 096 * @return This object. 097 */ 098 public Builder encoding(boolean value) { 099 encoding = value; 100 return this; 101 } 102 @Override /* Overridden from Builder */ 103 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 104 super.apply(type, apply); 105 return this; 106 } 107 108 @Override /* Overridden from Builder */ 109 public Builder debug(Boolean value) { 110 super.debug(value); 111 return this; 112 } 113 114 @Override /* Overridden from Builder */ 115 public Builder properties(Map<String,Object> value) { 116 super.properties(value); 117 return this; 118 } 119 120 @Override /* Overridden from Builder */ 121 public Builder property(String key, Object value) { 122 super.property(key, value); 123 return this; 124 } 125 126 @Override /* Overridden from Builder */ 127 public Builder unmodifiable() { 128 super.unmodifiable(); 129 return this; 130 } 131 132 @Override /* Overridden from Builder */ 133 public Builder locale(Locale value) { 134 super.locale(value); 135 return this; 136 } 137 138 @Override /* Overridden from Builder */ 139 public Builder localeDefault(Locale value) { 140 super.localeDefault(value); 141 return this; 142 } 143 144 @Override /* Overridden from Builder */ 145 public Builder mediaType(MediaType value) { 146 super.mediaType(value); 147 return this; 148 } 149 150 @Override /* Overridden from Builder */ 151 public Builder mediaTypeDefault(MediaType value) { 152 super.mediaTypeDefault(value); 153 return this; 154 } 155 156 @Override /* Overridden from Builder */ 157 public Builder timeZone(TimeZone value) { 158 super.timeZone(value); 159 return this; 160 } 161 162 @Override /* Overridden from Builder */ 163 public Builder timeZoneDefault(TimeZone value) { 164 super.timeZoneDefault(value); 165 return this; 166 } 167 168 @Override /* Overridden from Builder */ 169 public Builder javaMethod(Method value) { 170 super.javaMethod(value); 171 return this; 172 } 173 174 @Override /* Overridden from Builder */ 175 public Builder resolver(VarResolverSession value) { 176 super.resolver(value); 177 return this; 178 } 179 180 @Override /* Overridden from Builder */ 181 public Builder schema(HttpPartSchema value) { 182 super.schema(value); 183 return this; 184 } 185 186 @Override /* Overridden from Builder */ 187 public Builder schemaDefault(HttpPartSchema value) { 188 super.schemaDefault(value); 189 return this; 190 } 191 192 @Override /* Overridden from Builder */ 193 public Builder uriContext(UriContext value) { 194 super.uriContext(value); 195 return this; 196 } 197 198 @Override /* Overridden from Builder */ 199 public Builder fileCharset(Charset value) { 200 super.fileCharset(value); 201 return this; 202 } 203 204 @Override /* Overridden from Builder */ 205 public Builder streamCharset(Charset value) { 206 super.streamCharset(value); 207 return this; 208 } 209 210 @Override /* Overridden from Builder */ 211 public Builder useWhitespace(Boolean value) { 212 super.useWhitespace(value); 213 return this; 214 } 215 } 216 217 //----------------------------------------------------------------------------------------------------------------- 218 // Instance 219 //----------------------------------------------------------------------------------------------------------------- 220 221 private final UonSerializer ctx; 222 private final boolean plainTextParams; 223 224 /** 225 * Constructor. 226 * 227 * @param builder The builder for this object. 228 */ 229 public UonSerializerSession(Builder builder) { 230 super(builder); 231 ctx = builder.ctx; 232 plainTextParams = ctx.getParamFormat() == ParamFormat.PLAINTEXT; 233 } 234 235 /** 236 * Converts the specified output target object to an {@link UonWriter}. 237 * 238 * @param out The output target object. 239 * @return The output target object wrapped in an {@link UonWriter}. 240 * @throws IOException Thrown by underlying stream. 241 */ 242 protected final UonWriter getUonWriter(SerializerPipe out) throws IOException { 243 Object output = out.getRawOutput(); 244 if (output instanceof UonWriter) 245 return (UonWriter)output; 246 UonWriter w = new UonWriter(this, out.getWriter(), isUseWhitespace(), getMaxIndent(), isEncoding(), isTrimStrings(), plainTextParams, getQuoteChar(), getUriResolver()); 247 out.setWriter(w); 248 return w; 249 } 250 251 private final UonWriter getUonWriter(Writer out) throws Exception { 252 return new UonWriter(this, out, isUseWhitespace(), getMaxIndent(), isEncoding(), isTrimStrings(), plainTextParams, getQuoteChar(), getUriResolver()); 253 } 254 255 @Override /* Serializer */ 256 protected void doSerialize(SerializerPipe out, Object o) throws IOException, SerializeException { 257 serializeAnything(getUonWriter(out).i(getInitialDepth()), o, getExpectedRootType(o), "root", null); 258 } 259 260 /** 261 * Workhorse method. 262 * 263 * <p> 264 * Determines the type of object, and then calls the appropriate type-specific serialization method. 265 * 266 * @param out The writer to serialize to. 267 * @param o The object being serialized. 268 * @param eType The expected type of the object if this is a bean property. 269 * @param attrName 270 * The bean property name if this is a bean property. 271 * <jk>null</jk> if this isn't a bean property being serialized. 272 * @param pMeta The bean property metadata. 273 * @return The same writer passed in. 274 * @throws SerializeException Generic serialization error occurred. 275 */ 276 @SuppressWarnings({ "rawtypes" }) 277 protected SerializerWriter serializeAnything(UonWriter out, Object o, ClassMeta<?> eType, String attrName, BeanPropertyMeta pMeta) throws SerializeException { 278 279 if (o == null) { 280 out.appendObject(null, false); 281 return out; 282 } 283 284 if (eType == null) 285 eType = object(); 286 287 ClassMeta<?> aType; // The actual type 288 ClassMeta<?> sType; // The serialized type 289 290 aType = push2(attrName, o, eType); 291 boolean isRecursion = aType == null; 292 293 // Handle recursion 294 if (aType == null) { 295 o = null; 296 aType = object(); 297 } 298 299 // Handle Optional<X> 300 if (isOptional(aType)) { 301 o = getOptionalValue(o); 302 eType = getOptionalType(eType); 303 aType = getClassMetaForObject(o, object()); 304 } 305 306 sType = aType; 307 String typeName = getBeanTypeName(this, eType, aType, pMeta); 308 309 // Swap if necessary 310 ObjectSwap swap = aType.getSwap(this); 311 if (swap != null) { 312 o = swap(swap, o); 313 sType = swap.getSwapClassMeta(this); 314 315 // If the getSwapClass() method returns Object, we need to figure out 316 // the actual type now. 317 if (sType.isObject()) 318 sType = getClassMetaForObject(o); 319 } 320 321 // '\0' characters are considered null. 322 if (o == null || (sType.isChar() && ((Character)o).charValue() == 0)) 323 out.appendObject(null, false); 324 else if (sType.isBoolean()) 325 out.appendBoolean(o); 326 else if (sType.isNumber()) 327 out.appendNumber(o); 328 else if (sType.isBean()) 329 serializeBeanMap(out, toBeanMap(o), typeName); 330 else if (sType.isUri() || (pMeta != null && pMeta.isUri())) 331 out.appendUri(o); 332 else if (sType.isMap()) { 333 if (o instanceof BeanMap) 334 serializeBeanMap(out, (BeanMap)o, typeName); 335 else 336 serializeMap(out, (Map)o, eType); 337 } 338 else if (sType.isCollection()) { 339 serializeCollection(out, (Collection) o, eType); 340 } 341 else if (sType.isArray()) { 342 serializeCollection(out, toList(sType.getInnerClass(), o), eType); 343 } 344 else if (sType.isReader()) { 345 pipe((Reader)o, out, SerializerSession::handleThrown); 346 } 347 else if (sType.isInputStream()) { 348 pipe((InputStream)o, out, SerializerSession::handleThrown); 349 } 350 else { 351 out.appendObject(o, false); 352 } 353 354 if (! isRecursion) 355 pop(); 356 return out; 357 } 358 359 @SuppressWarnings({ "rawtypes", "unchecked" }) 360 private SerializerWriter serializeMap(UonWriter out, Map m, ClassMeta<?> type) throws SerializeException { 361 362 ClassMeta<?> keyType = type.getKeyType(), valueType = type.getValueType(); 363 364 if (! plainTextParams) 365 out.append('('); 366 367 Flag addComma = Flag.create(); 368 forEachEntry(m, x -> { 369 addComma.ifSet(()->out.append(',')).set(); 370 Object value = x.getValue(); 371 Object key = generalize(x.getKey(), keyType); 372 out.cr(indent).appendObject(key, false).append('='); 373 serializeAnything(out, value, valueType, toString(key), null); 374 }); 375 376 addComma.ifSet(()->out.cre(indent-1)); 377 378 if (! plainTextParams) 379 out.append(')'); 380 381 return out; 382 } 383 384 private SerializerWriter serializeBeanMap(UonWriter out, BeanMap<?> m, String typeName) throws SerializeException { 385 386 if (! plainTextParams) 387 out.append('('); 388 389 Flag addComma = Flag.create(); 390 391 if (typeName != null) { 392 BeanPropertyMeta pm = m.getMeta().getTypeProperty(); 393 out.cr(indent).appendObject(pm.getName(), false).append('=').appendObject(typeName, false); 394 addComma.set(); 395 } 396 397 Predicate<Object> checkNull = x -> isKeepNullProperties() || x != null; 398 m.forEachValue(checkNull, (pMeta,key,value,thrown) -> { 399 ClassMeta<?> cMeta = pMeta.getClassMeta(); 400 401 if (thrown != null) 402 onBeanGetterException(pMeta, thrown); 403 404 if (canIgnoreValue(cMeta, key, value)) 405 return; 406 407 addComma.ifSet(() -> out.append(',')).set(); 408 409 out.cr(indent).appendObject(key, false).append('='); 410 411 serializeAnything(out, value, cMeta, key, pMeta); 412 }); 413 414 if (m.size() > 0) 415 out.cre(indent-1); 416 if (! plainTextParams) 417 out.append(')'); 418 419 return out; 420 } 421 422 @SuppressWarnings({ "rawtypes", "unchecked" }) 423 private SerializerWriter serializeCollection(UonWriter out, Collection c, ClassMeta<?> type) throws SerializeException { 424 425 ClassMeta<?> elementType = type.getElementType(); 426 427 if (! plainTextParams) 428 out.append('@').append('('); 429 430 Flag addComma = Flag.create(); 431 forEachEntry(c, x -> { 432 addComma.ifSet(()->out.append(',')).set(); 433 out.cr(indent); 434 serializeAnything(out, x, elementType, "<iterator>", null); 435 }); 436 437 addComma.ifSet(()->out.cre(indent-1)); 438 if (! plainTextParams) 439 out.append(')'); 440 441 return out; 442 } 443 444 @Override /* HttpPartSerializer */ 445 public String serialize(HttpPartType type, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException { 446 try { 447 // Shortcut for simple types. 448 ClassMeta<?> cm = getClassMetaForObject(value); 449 if (cm != null && (schema == null || schema.getType() == HttpPartDataType.NO_TYPE)) { 450 if (cm.isNumber() || cm.isBoolean()) 451 return Mutaters.toString(value); 452 if (cm.isString()) { 453 String s = Mutaters.toString(value); 454 if (s.isEmpty() || ! UonUtils.needsQuotes(s)) 455 return s; 456 } 457 } 458 StringWriter w = new StringWriter(); 459 serializeAnything(getUonWriter(w).i(getInitialDepth()), value, getExpectedRootType(value), "root", null); 460 return w.toString(); 461 } catch (Exception e) { 462 throw asRuntimeException(e); 463 } 464 } 465 466 //----------------------------------------------------------------------------------------------------------------- 467 // Properties 468 //----------------------------------------------------------------------------------------------------------------- 469 470 /** 471 * Add <js>"_type"</js> properties when needed. 472 * 473 * @see UonSerializer.Builder#addBeanTypesUon() 474 * @return 475 * <jk>true</jk> if <js>"_type"</js> properties will be added to beans if their type cannot be inferred 476 * through reflection. 477 */ 478 @Override 479 protected final boolean isAddBeanTypes() { 480 return ctx.isAddBeanTypes(); 481 } 482 483 /** 484 * Encode non-valid URI characters. 485 * 486 * @see UonSerializer.Builder#encoding() 487 * @return 488 * <jk>true</jk> if non-valid URI characters should be encoded with <js>"%xx"</js> constructs. 489 */ 490 protected final boolean isEncoding() { 491 return ctx.isEncoding(); 492 } 493 494 /** 495 * Format to use for query/form-data/header values. 496 * 497 * @see UonSerializer.Builder#paramFormat(ParamFormat) 498 * @return 499 * Specifies the format to use for URL GET parameter keys and values. 500 */ 501 protected final ParamFormat getParamFormat() { 502 return ctx.getParamFormat(); 503 } 504 505 /** 506 * Quote character. 507 * 508 * @see UonSerializer.Builder#quoteCharUon(char) 509 * @return 510 * The character used for quoting attributes and values. 511 */ 512 @Override 513 protected final char getQuoteChar() { 514 return ctx.getQuoteChar(); 515 } 516}