org.jolokia.service.serializer.json.ObjectToJsonConverter Maven / Gradle / Ivy
package org.jolokia.service.serializer.json;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import javax.management.AttributeNotFoundException;
import org.jolokia.service.serializer.object.StringToObjectConverter;
import org.jolokia.server.core.service.serializer.SerializeOptions;
import org.jolokia.server.core.service.serializer.ValueFaultHandler;
import org.jolokia.server.core.util.LocalServiceFactory;
import org.jolokia.server.core.util.EscapeUtil;
/*
* Copyright 2009-2013 Roland Huss
*
* Licensed 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.
*/
/**
* A converter which converts attribute and return values
* into a JSON representation. It uses certain handlers for this which
* are registered in the constructor.
*
* Each handler gets a reference to this converter object so that it
* can use it for a recursive solution of nested objects.
*
* @author roland
* @since Apr 19, 2009
*/
public final class ObjectToJsonConverter {
// List of dedicated handlers used for delegation in serialization/deserializatin
private List handlers;
private ArrayExtractor arrayExtractor;
// Thread-Local set in order to prevent infinite recursions
private ThreadLocal stackContextLocal = new ThreadLocal();
// Used for converting string to objects when setting attributes
private StringToObjectConverter stringToObjectConverter;
// Definition of simplifiers
private static final String SIMPLIFIERS_DEFAULT_DEF = "META-INF/jolokia/simplifiers-default";
private static final String SIMPLIFIERS_DEF = "META-INF/jolokia/simplifiers";
/**
* New object-to-json converter
*
* @param pStringToObjectConverter used when setting values
* @param pSimplifyHandlers a bunch of simplifiers-default used for mangling the conversion result
*/
public ObjectToJsonConverter(StringToObjectConverter pStringToObjectConverter) {
handlers = new ArrayList();
// TabularDataExtractor must be before MapExtractor
// since TabularDataSupport isa Map
handlers.add(new TabularDataExtractor());
handlers.add(new CompositeDataExtractor());
// Collection handlers
handlers.add(new ListExtractor());
handlers.add(new MapExtractor());
handlers.add(new CollectionExtractor());
// Special, well known objects
addSimplifiers(handlers);
// Enum handling
handlers.add(new EnumExtractor());
// Special date handling
handlers.add(new DateExtractor());
// Must be last in handlers, used default algorithm
handlers.add(new BeanExtractor());
arrayExtractor = new ArrayExtractor();
stringToObjectConverter = pStringToObjectConverter;
}
/**
* Convert the return value to a JSON object.
*
* @param pValue the value to convert
* @param pPathParts path parts to use for extraction
* @param pOptions options used for parsing
* @return the converter object. This either a subclass of {@link org.json.simple.JSONAware} or a basic data type like String or Long.
* @throws AttributeNotFoundException if within an path an attribute could not be found
*/
public Object serialize(Object pValue, List pPathParts, SerializeOptions pOptions)
throws AttributeNotFoundException {
Stack extraStack = pPathParts != null ? EscapeUtil.reversePath(pPathParts) : new Stack();
return extractObjectWithContext(pValue, extraStack, pOptions, true);
}
/**
* Set an inner value of a complex object. A given path must point to the attribute/index to set within the outer object.
*
* @param pOuterObject the object to dive in
* @param pNewValue the value to set
* @param pPathParts the path within the outer object. This object will be modified and must be a modifiable list.
* @return the old value
*
* @throws AttributeNotFoundException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public Object setInnerValue(Object pOuterObject, Object pNewValue, List pPathParts)
throws AttributeNotFoundException, IllegalAccessException, InvocationTargetException {
String lastPathElement = pPathParts.remove(pPathParts.size()-1);
Stack extraStack = EscapeUtil.reversePath(pPathParts);
// Get the object pointed to do with path-1
// We are using no limits here, since a path must have been given (see above), and hence we should
// be save anyway.
Object inner = extractObjectWithContext(pOuterObject, extraStack, SerializeOptions.DEFAULT, false);
// Set the attribute pointed to by the path elements
// (depending of the parent object's type)
return setObjectValue(inner, lastPathElement, pNewValue);
}
/**
* Related to {@link #extractObjectWithContext} except that
* it does not setup a context. This method is called back from the
* various extractors to recursively continue the extraction, hence it is public.
*
* This method must not be used as entry point for serialization.
* Use {@link #serialize(Object, List, SerializeOptions)} or
* {@link #setInnerValue(Object, Object, List)} instead.
*
* @param pValue value to extract from
* @param pPathParts stack for diving into the object
* @param pJsonify whether a JSON representation {@link org.json.simple.JSONObject}
* @return extracted object either in native format or as {@link org.json.simple.JSONObject}
* @throws AttributeNotFoundException if an attribute is not found during traversal
*/
public Object extractObject(Object pValue, Stack pPathParts, boolean pJsonify)
throws AttributeNotFoundException {
ObjectSerializationContext stackContext = stackContextLocal.get();
String limitReached = checkForLimits(pValue, stackContext);
Stack pathStack = pPathParts != null ? pPathParts : new Stack();
if (limitReached != null) {
return limitReached;
}
try {
stackContext.push(pValue);
if (pValue == null) {
return pathStack.isEmpty() ?
null :
stackContext.getValueFaultHandler().handleException(
new AttributeNotFoundException("Cannot apply a path to an null value"));
}
if (pValue.getClass().isArray()) {
// Special handling for arrays
return arrayExtractor.extractObject(this,pValue,pathStack,pJsonify);
}
return callHandler(pValue, pathStack, pJsonify);
} finally {
stackContext.pop();
}
}
// =================================================================================================
/**
* Handle a value which means to dive into the internal of a complex object
* (if pExtraArgs
is not null) and/or to convert
* it to JSON (if pJsonify
is true).
*
*
* @param pValue value to extract from
* @param pExtraArgs stack used for diving in to the value
* @param pOpts options from which various processing
* parameters (like maxDepth, maxCollectionSize and maxObjects) are taken and put
* into context in order to influence the object traversal.
* @param pJsonify whether the result should be returned as an JSON object
* @return extracted value, either natively or as JSON
* @throws AttributeNotFoundException if during traversal an attribute is not found as specified in the stack
*/
private Object extractObjectWithContext(Object pValue, Stack pExtraArgs, SerializeOptions pOpts, boolean pJsonify)
throws AttributeNotFoundException {
Object jsonResult;
setupContext(pOpts);
try {
jsonResult = extractObject(pValue, pExtraArgs, pJsonify);
} catch (ValueFaultHandler.AttributeFilteredException exp) {
jsonResult = null;
} finally {
clearContext();
}
return jsonResult;
}
/**
* Set an value of an inner object
*
* @param pInner the inner object
* @param pAttribute the attribute to set
* @param pValue the value to set
* @return the old value
* @throws IllegalAccessException if the reflection code fails during setting of the value
* @throws InvocationTargetException reflection error
*/
private Object setObjectValue(Object pInner, String pAttribute, Object pValue)
throws IllegalAccessException, InvocationTargetException {
// Call various handlers depending on the type of the inner object, as is extract Object
Class clazz = pInner.getClass();
if (clazz.isArray()) {
return arrayExtractor.setObjectValue(stringToObjectConverter,pInner,pAttribute,pValue);
}
Extractor handler = getExtractor(clazz);
if (handler != null) {
return handler.setObjectValue(stringToObjectConverter,pInner,pAttribute,pValue);
} else {
throw new IllegalStateException(
"Internal error: No handler found for class " + clazz + " for setting object value." +
" (object: " + pInner + ", attribute: " + pAttribute + ", value: " + pValue + ")");
}
}
// =================================================================================
/**
* Get the length of an extracted collection, but not larger than the configured limit.
*
* @param originalLength the orginal length
* @return the original length if is smaller than then the configured maximum length. Otherwise the
* maximum length is returned.
*/
int getCollectionLength(int originalLength) {
ObjectSerializationContext ctx = stackContextLocal.get();
return ctx.getCollectionSizeTruncated(originalLength);
}
/**
* Get the fault handler used for dealing with exceptions during value extraction.
*
* @return the fault handler
*/
public ValueFaultHandler getValueFaultHandler() {
ObjectSerializationContext ctx = stackContextLocal.get();
return ctx.getValueFaultHandler();
}
/**
* Clear the context used for counting objects and limits
*/
void clearContext() {
stackContextLocal.remove();
}
/**
* Setup the context with hard limits and defaults
*/
void setupContext() {
setupContext(new SerializeOptions.Builder().build());
}
/**
* Setup the context with the limits given in the request or with the default limits if not. In all cases,
* hard limits as defined in the servlet configuration are never exceeded.
*
* @param pOpts options used for parsing.
*/
void setupContext(SerializeOptions pOpts) {
ObjectSerializationContext stackContext = new ObjectSerializationContext(pOpts);
stackContextLocal.set(stackContext);
}
// =================================================================================
// Get the extractor for a certain class
private Extractor getExtractor(Class pClazz) {
for (Extractor handler : handlers) {
if (handler.canSetValue() && handler.getType() != null && handler.getType().isAssignableFrom(pClazz)) {
return handler;
}
}
return null;
}
private String checkForLimits(Object pValue, ObjectSerializationContext pStackContext) {
if (pValue != null) {
if (pStackContext.maxDepthReached()) {
// We use its string representation.
return pValue.toString();
}
if (pStackContext.alreadyVisited(pValue)) {
return "[Reference " + pValue.getClass().getName() + "@" + Integer.toHexString(pValue.hashCode()) + "]";
}
}
if (pStackContext.maxObjectsExceeded()) {
return "[Object limit exceeded]";
}
return null;
}
private Object callHandler(Object pValue, Stack pPathParts, boolean pJsonify)
throws AttributeNotFoundException {
Class pClazz = pValue.getClass();
for (Extractor handler : handlers) {
if (handler.getType() != null && handler.getType().isAssignableFrom(pClazz)) {
return handler.extractObject(this,pValue,pPathParts,pJsonify);
}
}
throw new IllegalStateException(
"Internal error: No handler found for class " + pClazz +
" (object: " + pValue + ", extraArgs: " + pPathParts + ")");
}
// Used for testing only. Hence final and package local
ThreadLocal getStackContextLocal() {
return stackContextLocal;
}
// Simplifiers are added either explicitely or by reflection from a subpackage
private void addSimplifiers(List pHandlers) {
// Add all
pHandlers.addAll(LocalServiceFactory.createServices(this.getClass().getClassLoader(),
SIMPLIFIERS_DEFAULT_DEF, SIMPLIFIERS_DEF));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy