/*
* Copyright 2002-2016 the original author or authors.
*
* 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.
*/
package org.springframework.integration.transformer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.integration.support.json.JsonObjectMapper;
import org.springframework.integration.support.json.JsonObjectMapperProvider;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Will transform an object graph into a Map. It supports a conventional Map (map of maps) where complex attributes are
* represented as Map values as well as a flat Map where keys document the path to the value. By default it will
* transform to a flat Map. If you need to transform to a Map of Maps set the 'shouldFlattenKeys' property to 'false'
* via the {@link ObjectToMapTransformer#setShouldFlattenKeys(boolean)} method. It supports Collections, Maps and Arrays
* which means that for flat maps it will flatten an Object's properties. Below is an example showing how a flattened
* Object hierarchy is represented when 'shouldFlattenKeys' is TRUE.<br>
*
* <code>
* public class Person {
* public String name = "John";
* public Address address = new Address();
* }
* public class Address {
* private String street = "123 Main Street";
* }
* </code>
*
* The resulting Map would look like this:
* <code>
* {name=John, address.street=123 Main Street}
* </code>
*
* @author Oleg Zhurakousky
* @author Artem Bilan
* @author Gary Russell
* @since 2.0
*/
public class ObjectToMapTransformer extends AbstractPayloadTransformer<Object, Map<?, ?>> {
private final JsonObjectMapper<?, ?> jsonObjectMapper = JsonObjectMapperProvider.newInstance();
private volatile boolean shouldFlattenKeys = true;
public void setShouldFlattenKeys(boolean shouldFlattenKeys) {
this.shouldFlattenKeys = shouldFlattenKeys;
}
@Override
@SuppressWarnings("unchecked")
protected Map<String, Object> transformPayload(Object payload) throws Exception {
Map<String, Object> result = this.jsonObjectMapper.fromJson(this.jsonObjectMapper.toJson(payload), Map.class);
if (this.shouldFlattenKeys) {
result = this.flattenMap(result);
}
return result;
}
@Override
public String getComponentType() {
return "object-to-map-transformer";
}
@SuppressWarnings("unchecked")
private void doProcessElement(String propertyPrefix, Object element, Map<String, Object> resultMap) {
if (element instanceof Map) {
this.doFlatten(propertyPrefix, (Map<String, Object>) element, resultMap);
}
else if (element instanceof Collection) {
this.doProcessCollection(propertyPrefix, (Collection<?>) element, resultMap);
}
else if (element != null && element.getClass().isArray()) {
Collection<?> collection = CollectionUtils.arrayToList(element);
this.doProcessCollection(propertyPrefix, collection, resultMap);
}
else {
resultMap.put(propertyPrefix, element);
}
}
private Map<String, Object> flattenMap(Map<String, Object> result) {
Map<String, Object> resultMap = new HashMap<String, Object>();
this.doFlatten("", result, resultMap);
return resultMap;
}
private void doFlatten(String propertyPrefix, Map<String, Object> inputMap, Map<String, Object> resultMap) {
if (StringUtils.hasText(propertyPrefix)) {
propertyPrefix = propertyPrefix + ".";
}
for (Entry<String, Object> entry : inputMap.entrySet()) {
this.doProcessElement(propertyPrefix + entry.getKey(), entry.getValue(), resultMap);
}
}
private void doProcessCollection(String propertyPrefix, Collection<?> list, Map<String, Object> resultMap) {
int counter = 0;
for (Object element : list) {
this.doProcessElement(propertyPrefix + "[" + counter + "]", element, resultMap);
counter++;
}
}
}