Java tutorial
/* * Copyright 2013-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.cloud.stream.binder; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.core.serializer.support.SerializationFailedException; import org.springframework.expression.EvaluationContext; import org.springframework.integration.codec.Codec; import org.springframework.integration.expression.ExpressionUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.retry.backoff.ExponentialBackOffPolicy; import org.springframework.retry.policy.SimpleRetryPolicy; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Base class for {@link Binder} implementations. * * @author David Turanski * @author Gary Russell * @author Ilayaperumal Gopinathan * @author Mark Fisher * @author Marius Bogoevici */ public abstract class AbstractBinder<T, C extends ConsumerProperties, P extends ProducerProperties> implements ApplicationContextAware, InitializingBean, Binder<T, C, P> { /** * The delimiter between a group and index when constructing a binder * consumer/producer. */ private static final String GROUP_INDEX_DELIMITER = "."; protected final Log logger = LogFactory.getLog(getClass()); private volatile AbstractApplicationContext applicationContext; private volatile Codec codec; private final StringConvertingContentTypeResolver contentTypeResolver = new StringConvertingContentTypeResolver(); private volatile EvaluationContext evaluationContext; private volatile Map<String, Class<?>> payloadTypeCache = new ConcurrentHashMap<>(); /** * For binder implementations that support a prefix, apply the prefix to the name. * * @param prefix the prefix. * @param name the name. */ public static String applyPrefix(String prefix, String name) { return prefix + name; } /** * For binder implementations that support dead lettering, construct the name of the * dead letter entity for the underlying pipe name. * * @param name the name. */ public static String constructDLQName(String name) { return name + ".dlq"; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); this.applicationContext = (AbstractApplicationContext) applicationContext; } protected AbstractApplicationContext getApplicationContext() { return this.applicationContext; } protected ConfigurableListableBeanFactory getBeanFactory() { return this.applicationContext.getBeanFactory(); } public void setCodec(Codec codec) { this.codec = codec; } public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { this.evaluationContext = evaluationContext; } @Override public final void afterPropertiesSet() throws Exception { Assert.notNull(this.applicationContext, "The 'applicationContext' property must not be null"); if (this.evaluationContext == null) { this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory()); } onInit(); } /** * Subclasses may implement this method to perform any necessary initialization. It * will be invoked from {@link #afterPropertiesSet()} which is itself {@code final}. */ protected void onInit() throws Exception { // no-op default } @Override public final Binding<T> bindConsumer(String name, String group, T target, C properties) { if (StringUtils.isEmpty(group)) { Assert.isTrue(!properties.isPartitioned(), "A consumer group is required for a partitioned subscription"); } return doBindConsumer(name, group, target, properties); } protected abstract Binding<T> doBindConsumer(String name, String group, T inputTarget, C properties); @Override public final Binding<T> bindProducer(String name, T outboundBindTarget, P properties) { return doBindProducer(name, outboundBindTarget, properties); } protected abstract Binding<T> doBindProducer(String name, T outboundBindTarget, P properties); /** * Construct a name comprised of the name and group. * * @param name the name. * @param group the group. * @return the constructed name. */ protected final String groupedName(String name, String group) { return name + GROUP_INDEX_DELIMITER + (StringUtils.hasText(group) ? group : "default"); } protected final MessageValues serializePayloadIfNecessary(Message<?> message) { Object originalPayload = message.getPayload(); Object originalContentType = message.getHeaders().get(MessageHeaders.CONTENT_TYPE); // Pass content type as String since some transport adapters will exclude // CONTENT_TYPE Header otherwise Object contentType = JavaClassMimeTypeConversion .mimeTypeFromObject(originalPayload, ObjectUtils.nullSafeToString(originalContentType)).toString(); Object payload = serializePayloadIfNecessary(originalPayload); MessageValues messageValues = new MessageValues(message); messageValues.setPayload(payload); messageValues.put(MessageHeaders.CONTENT_TYPE, contentType); if (originalContentType != null && !originalContentType.toString().equals(contentType.toString())) { messageValues.put(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE, originalContentType.toString()); } return messageValues; } protected final byte[] serializePayloadIfNecessary(Object originalPayload) { if (originalPayload instanceof byte[]) { return (byte[]) originalPayload; } else { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { if (originalPayload instanceof String) { return ((String) originalPayload).getBytes("UTF-8"); } this.codec.encode(originalPayload, bos); return bos.toByteArray(); } catch (IOException e) { throw new SerializationFailedException( "unable to serialize payload [" + originalPayload.getClass().getName() + "]", e); } } } protected final MessageValues deserializePayloadIfNecessary(Message<?> message) { return deserializePayloadIfNecessary(new MessageValues(message)); } protected final MessageValues deserializePayloadIfNecessary(MessageValues messageValues) { Object originalPayload = messageValues.getPayload(); MimeType contentType = this.contentTypeResolver.resolve(messageValues); Object payload = deserializePayload(originalPayload, contentType); if (payload != null) { messageValues.setPayload(payload); Object originalContentType = messageValues.get(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE); // Reset content-type only if the original content type is not null (when // receiving messages from // non-SCSt applications). if (originalContentType != null) { messageValues.put(MessageHeaders.CONTENT_TYPE, originalContentType); messageValues.remove(BinderHeaders.BINDER_ORIGINAL_CONTENT_TYPE); } } return messageValues; } private Object deserializePayload(Object payload, MimeType contentType) { if (payload instanceof byte[]) { if (contentType == null || MimeTypeUtils.APPLICATION_OCTET_STREAM.equals(contentType)) { return payload; } else { return deserializePayload((byte[]) payload, contentType); } } return payload; } private Object deserializePayload(byte[] bytes, MimeType contentType) { if ("text".equalsIgnoreCase(contentType.getType()) || MimeTypeUtils.APPLICATION_JSON.equals(contentType)) { try { return new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { String errorMessage = "unable to deserialize [java.lang.String]. Encoding not supported. " + e.getMessage(); logger.error(errorMessage); throw new SerializationFailedException(errorMessage, e); } } else { String className = JavaClassMimeTypeConversion.classNameFromMimeType(contentType); try { // Cache types to avoid unnecessary ClassUtils.forName calls. Class<?> targetType = this.payloadTypeCache.get(className); if (targetType == null) { targetType = ClassUtils.forName(className, null); this.payloadTypeCache.put(className, targetType); } return this.codec.decode(bytes, targetType); } // catch all exceptions that could occur during de-serialization catch (Exception e) { String errorMessage = "Unable to deserialize [" + className + "] using the contentType [" + contentType + "] " + e.getMessage(); logger.error(errorMessage); throw new SerializationFailedException(errorMessage, e); } } } protected String buildPartitionRoutingExpression(String expressionRoot) { return "'" + expressionRoot + "-' + headers['" + BinderHeaders.PARTITION_HEADER + "']"; } /** * Create and configure a retry template. * * @param properties The properties. * @return The retry template */ public RetryTemplate buildRetryTemplate(ConsumerProperties properties) { RetryTemplate template = new RetryTemplate(); SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); retryPolicy.setMaxAttempts(properties.getMaxAttempts()); ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(properties.getBackOffInitialInterval()); backOffPolicy.setMultiplier(properties.getBackOffMultiplier()); backOffPolicy.setMaxInterval(properties.getBackOffMaxInterval()); template.setRetryPolicy(retryPolicy); template.setBackOffPolicy(backOffPolicy); return template; } /** * Handles representing any java class as a {@link MimeType}. * * @author David Turanski * @author Ilayaperumal Gopinathan */ public abstract static class JavaClassMimeTypeConversion { private static ConcurrentMap<String, MimeType> mimeTypesCache = new ConcurrentHashMap<>(); static MimeType mimeTypeFromObject(Object payload, String originalContentType) { Assert.notNull(payload, "payload object cannot be null."); if (payload instanceof byte[]) { return MimeTypeUtils.APPLICATION_OCTET_STREAM; } if (payload instanceof String) { return MimeTypeUtils.APPLICATION_JSON_VALUE.equals(originalContentType) ? MimeTypeUtils.APPLICATION_JSON : MimeTypeUtils.TEXT_PLAIN; } String className = payload.getClass().getName(); MimeType mimeType = mimeTypesCache.get(className); if (mimeType == null) { String modifiedClassName = className; if (payload.getClass().isArray()) { // Need to remove trailing ';' for an object array, e.g. // "[Ljava.lang.String;" or multi-dimensional // "[[[Ljava.lang.String;" if (modifiedClassName.endsWith(";")) { modifiedClassName = modifiedClassName.substring(0, modifiedClassName.length() - 1); } // Wrap in quotes to handle the illegal '[' character modifiedClassName = "\"" + modifiedClassName + "\""; } mimeType = MimeType.valueOf("application/x-java-object;type=" + modifiedClassName); mimeTypesCache.put(className, mimeType); } return mimeType; } static String classNameFromMimeType(MimeType mimeType) { Assert.notNull(mimeType, "mimeType cannot be null."); String className = mimeType.getParameter("type"); if (className == null) { return null; } // unwrap quotes if any className = className.replace("\"", ""); // restore trailing ';' if (className.contains("[L")) { className += ";"; } return className; } } }