org.apache.juneau.serializer.SerializerSession Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau.serializer;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.io.*;
import java.lang.reflect.*;
import java.text.*;
import java.util.*;
import org.apache.juneau.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.soap.*;
import org.apache.juneau.transform.*;
/**
* Serializer session that lives for the duration of a single use of {@link Serializer}.
*
*
* Used by serializers for the following purposes:
*
* -
* Keeping track of how deep it is in a model for indentation purposes.
*
-
* Ensuring infinite loops don't occur by setting a limit on how deep to traverse a model.
*
-
* Ensuring infinite loops don't occur from loops in the model (when detectRecursions is enabled.
*
-
* Allowing serializer properties to be overridden on method calls.
*
*
*
* This class is NOT thread safe.
* It is typically discarded after one-time use although it can be reused within the same thread.
*/
public abstract class SerializerSession extends BeanTraverseSession {
private final Serializer ctx;
private final UriResolver uriResolver;
private final Method javaMethod; // Java method that invoked this serializer.
// Writable properties
private final boolean useWhitespace;
private final SerializerListener listener;
/**
* Create a new session using properties specified in the context.
*
* @param ctx
* The context creating this session object.
* The context contains all the configuration settings for this object.
* Can be null .
* @param args
* Runtime arguments.
* These specify session-level information such as locale and URI context.
* It also include session-level properties that override the properties defined on the bean and
* serializer contexts.
*/
protected SerializerSession(Serializer ctx, SerializerSessionArgs args) {
super(ctx, args == null ? SerializerSessionArgs.DEFAULT : args);
this.ctx = ctx;
args = args == null ? SerializerSessionArgs.DEFAULT : args;
this.javaMethod = args.javaMethod;
this.uriResolver = new UriResolver(ctx.getUriResolution(), ctx.getUriRelativity(), args.uriContext == null ? ctx.getUriContext() : args.uriContext);
this.listener = newInstance(SerializerListener.class, ctx.getListener());
this.useWhitespace = args.useWhitespace != null ? args.useWhitespace : ctx.isUseWhitespace();
}
/**
* Default constructor.
*
* @param args
* Runtime arguments.
* These specify session-level information such as locale and URI context.
* It also include session-level properties that override the properties defined on the bean and
* serializer contexts.
*/
protected SerializerSession(SerializerSessionArgs args) {
this(Serializer.DEFAULT, args);
}
@Override /* Session */
public ObjectMap asMap() {
return super.asMap()
.append("SerializerSession", new ObjectMap()
.append("uriResolver", uriResolver)
);
}
/**
* Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into
* a stream or reader.
*
* @param output
* The output location.
*
For character-based serializers, this can be any of the following types:
*
* - {@link Writer}
*
- {@link OutputStream} - Output will be written as UTF-8 encoded stream.
*
- {@link File} - Output will be written as system-default encoded stream.
*
- {@link StringBuilder}
*
*
For byte-based serializers, this can be any of the following types:
*
* - {@link OutputStream}
*
- {@link File}
*
* @return
* A new {@link ParserPipe} wrapper around the specified input object.
*/
protected SerializerPipe createPipe(Object output) {
return new SerializerPipe(output);
}
//-----------------------------------------------------------------------------------------------------------------
// Abstract methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Serializes a POJO to the specified output stream or writer.
*
*
* This method should NOT close the context object.
*
* @param pipe Where to send the output from the serializer.
* @param o The object to serialize.
* @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed.
*/
protected abstract void doSerialize(SerializerPipe pipe, Object o) throws Exception;
/**
* Shortcut method for serializing objects directly to either a String
or byte []
* depending on the serializer type.
*
* @param o The object to serialize.
* @return
* The serialized object.
*
Character-based serializers will return a String
.
*
Stream-based serializers will return a byte []
.
* @throws SerializeException If a problem occurred trying to convert the output.
*/
public abstract Object serialize(Object o) throws SerializeException;
/**
* Shortcut method for serializing an object to a String.
*
* @param o The object to serialize.
* @return
* The serialized object.
*
Character-based serializers will return a String
*
Stream-based serializers will return a byte []
converted to a string based on the {@link OutputStreamSerializer#OSSERIALIZER_binaryFormat} setting.
* @throws SerializeException If a problem occurred trying to convert the output.
*/
public abstract String serializeToString(Object o) throws SerializeException;
/**
* Returns true if this serializer subclasses from {@link WriterSerializer}.
*
* @return true if this serializer subclasses from {@link WriterSerializer}.
*/
public abstract boolean isWriterSerializer();
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Serialize the specified object using the specified session.
*
* @param out Where to send the output from the serializer.
* @param o The object to serialize.
* @throws SerializeException If a problem occurred trying to convert the output.
*/
public final void serialize(Object o, Object out) throws SerializeException {
try (SerializerPipe pipe = createPipe(out)) {
doSerialize(pipe, o);
} catch (SerializeException e) {
throw e;
} catch (StackOverflowError e) {
throw new SerializeException(this,
"Stack overflow occurred. This can occur when trying to serialize models containing loops. It's recommended you use the BeanTraverseContext.BEANTRAVERSE_detectRecursions setting to help locate the loop.").initCause(e);
} catch (Exception e) {
throw new SerializeException(this, e);
} finally {
checkForWarnings();
}
}
/**
* Returns the Java method that invoked this serializer.
*
*
* When using the REST API, this is the Java method invoked by the REST call.
* Can be used to access annotations defined on the method or class.
*
* @return The Java method that invoked this serializer.
*/
protected final Method getJavaMethod() {
return javaMethod;
}
/**
* Returns the URI resolver.
*
* @return The URI resolver.
*/
protected final UriResolver getUriResolver() {
return uriResolver;
}
/**
* Specialized warning when an exception is thrown while executing a bean getter.
*
* @param p The bean map entry representing the bean property.
* @param t The throwable that the bean getter threw.
*/
protected final void onBeanGetterException(BeanPropertyMeta p, Throwable t) {
if (listener != null)
listener.onBeanGetterException(this, t, p);
String prefix = (isDebug() ? getStack(false) + ": " : "");
addWarning("{0}Could not call getValue() on property ''{1}'' of class ''{2}'', exception = {3}", prefix,
p.getName(), p.getBeanMeta().getClassMeta(), t.getLocalizedMessage());
}
/**
* Logs a warning message.
*
* @param t The throwable that was thrown (if there was one).
* @param msg The warning message.
* @param args Optional {@link MessageFormat}-style arguments.
*/
@Override
protected void onError(Throwable t, String msg, Object... args) {
if (listener != null)
listener.onError(this, t, format(msg, args));
super.onError(t, msg, args);
}
/**
* Trims the specified string if {@link SerializerSession#isTrimStrings()} returns true .
*
* @param o The input string to trim.
* @return The trimmed string, or null if the input was null .
*/
public final String trim(Object o) {
if (o == null)
return null;
String s = o.toString();
if (isTrimStrings())
s = s.trim();
return s;
}
/**
* Generalize the specified object if a POJO swap is associated with it.
*
* @param o The object to generalize.
* @param type The type of object.
* @return The generalized object, or null if the object is null .
* @throws SerializeException If a problem occurred trying to convert the output.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected final Object generalize(Object o, ClassMeta> type) throws SerializeException {
try {
if (o == null)
return null;
PojoSwap f = (type == null || type.isObject() ? getClassMeta(o.getClass()).getPojoSwap(this) : type.getPojoSwap(this));
if (f == null)
return o;
return f.swap(this, o);
} catch (SerializeException e) {
throw e;
} catch (Exception e) {
throw new SerializeException(e);
}
}
/**
* Returns true if the specified value should not be serialized.
*
* @param cm The class type of the object being serialized.
* @param attrName The bean attribute name, or null if this isn't a bean attribute.
* @param value The object being serialized.
* @return true if the specified value should not be serialized.
* @throws SerializeException If recursion occurred.
*/
public final boolean canIgnoreValue(ClassMeta> cm, String attrName, Object value) throws SerializeException {
if (isTrimNullProperties() && value == null)
return true;
if (value == null)
return false;
if (cm == null)
cm = object();
if (isTrimEmptyCollections()) {
if (cm.isArray() || (cm.isObject() && value.getClass().isArray())) {
if (((Object[])value).length == 0)
return true;
}
if (cm.isCollection() || (cm.isObject() && isParentClass(Collection.class, value.getClass()))) {
if (((Collection>)value).isEmpty())
return true;
}
}
if (isTrimEmptyMaps()) {
if (cm.isMap() || (cm.isObject() && isParentClass(Map.class, value.getClass()))) {
if (((Map,?>)value).isEmpty())
return true;
}
}
try {
if (isTrimNullProperties() && willRecurse(attrName, value, cm))
return true;
} catch (BeanRecursionException e) {
throw new SerializeException(e);
}
return false;
}
/**
* Sorts the specified map if {@link SerializerSession#isSortMaps()} returns true .
*
* @param m The map being sorted.
* @return A new sorted {@link TreeMap}.
*/
public final Map sort(Map m) {
if (isSortMaps() && m != null && (! m.isEmpty()) && m.keySet().iterator().next() instanceof Comparable>)
return new TreeMap<>(m);
return m;
}
/**
* Sorts the specified collection if {@link SerializerSession#isSortCollections()} returns true .
*
* @param c The collection being sorted.
* @return A new sorted {@link TreeSet}.
*/
public final Collection sort(Collection c) {
if (isSortCollections() && c != null && (! c.isEmpty()) && c.iterator().next() instanceof Comparable>)
return new TreeSet<>(c);
return c;
}
/**
* Converts the contents of the specified object array to a list.
*
*
* Works on both object and primitive arrays.
*
*
* In the case of multi-dimensional arrays, the outgoing list will contain elements of type n-1 dimension.
* i.e. if {@code type} is int [][]
then {@code list} will have entries of type
* int []
.
*
* @param type The type of array.
* @param array The array being converted.
* @return The array as a list.
*/
protected static final List