/* * 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.json; import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.integration.support.json.JsonObjectMapper; import org.springframework.integration.support.json.JsonObjectMapperProvider; import org.springframework.integration.transformer.AbstractTransformer; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.util.Assert; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.StringUtils; /** * Transformer implementation that converts a payload instance into a JSON string * representation. By default this transformer uses * {@linkplain org.springframework.integration.support.json.JsonObjectMapperProvider} * factory to get an instance of a Jackson or Jackson 2 JSON-processor * {@linkplain JsonObjectMapper} implementation depending on the jackson-databind or * jackson-mapper-asl libs on the classpath. Any other {@linkplain JsonObjectMapper} * implementation can be provided. * <p>Since version 3.0, adds headers to represent the object types that were mapped * from (including one level of container and Map content types). These headers * are compatible with the Spring AMQP Json type mapper such that messages mapped * or converted by either technology are compatible. One difference, however, is the * Spring AMQP converter, when converting to JSON, sets the header types to the class * name. This transformer sets the header types to the class itself. * <p>The compatibility is achieved because, when mapping the Spring Integration * message in the outbound endpoint (via the {@code DefaultAmqpHeaderMapper}), the * classes are converted to String at that time. * <p>Note: the first element of container/map types are used to determine the * container/map content types. If the first element is null, the type is set to * {@link Object}. * * @author Mark Fisher * @author James Carr * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan * @since 2.0 */ public class ObjectToJsonTransformer extends AbstractTransformer { public enum ResultType { STRING, NODE } public static final String JSON_CONTENT_TYPE = "application/json"; private final JsonObjectMapper<?, ?> jsonObjectMapper; private final ResultType resultType; private volatile String contentType = JSON_CONTENT_TYPE; private volatile boolean contentTypeExplicitlySet = false; public ObjectToJsonTransformer() { this(JsonObjectMapperProvider.newInstance()); } public ObjectToJsonTransformer(JsonObjectMapper<?, ?> jsonObjectMapper) { this(jsonObjectMapper, ResultType.STRING); } public ObjectToJsonTransformer(ResultType resultType) { this(JsonObjectMapperProvider.newInstance(), resultType); } public ObjectToJsonTransformer(JsonObjectMapper<?, ?> jsonObjectMapper, ResultType resultType) { Assert.notNull(jsonObjectMapper, "jsonObjectMapper must not be null"); Assert.notNull(resultType, "'resultType' must not be null"); this.jsonObjectMapper = jsonObjectMapper; this.resultType = resultType; } /** * Sets the content-type header value * * @param contentType The content type. */ public void setContentType(String contentType) { // only null assertion is needed since "" is a valid value Assert.notNull(contentType, "'contentType' must not be null"); this.contentTypeExplicitlySet = true; this.contentType = contentType.trim(); } @Override public String getComponentType() { return "object-to-json-transformer"; } @Override protected Object doTransform(Message<?> message) throws Exception { Object payload = ResultType.STRING.equals(this.resultType) ? this.jsonObjectMapper.toJson(message.getPayload()) : this.jsonObjectMapper.toJsonNode(message.getPayload()); AbstractIntegrationMessageBuilder<Object> messageBuilder = this.getMessageBuilderFactory().withPayload(payload); LinkedCaseInsensitiveMap<Object> headers = new LinkedCaseInsensitiveMap<Object>(); headers.putAll(message.getHeaders()); if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) { if (this.contentTypeExplicitlySet) { // override, unless empty if (StringUtils.hasLength(this.contentType)) { headers.put(MessageHeaders.CONTENT_TYPE, this.contentType); } } } else if (StringUtils.hasLength(this.contentType)) { headers.put(MessageHeaders.CONTENT_TYPE, this.contentType); } this.jsonObjectMapper.populateJavaTypes(headers, message.getPayload()); messageBuilder.copyHeaders(headers); return messageBuilder.build(); } }