001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.socket.adapter.standard; 018 019import java.nio.ByteBuffer; 020 021import javax.websocket.DecodeException; 022import javax.websocket.Decoder; 023import javax.websocket.EncodeException; 024import javax.websocket.Encoder; 025import javax.websocket.EndpointConfig; 026 027import org.springframework.beans.BeansException; 028import org.springframework.beans.factory.annotation.Autowired; 029import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 030import org.springframework.context.ApplicationContext; 031import org.springframework.context.ConfigurableApplicationContext; 032import org.springframework.core.GenericTypeResolver; 033import org.springframework.core.convert.ConversionException; 034import org.springframework.core.convert.ConversionService; 035import org.springframework.core.convert.TypeDescriptor; 036import org.springframework.lang.Nullable; 037import org.springframework.util.Assert; 038import org.springframework.web.context.ContextLoader; 039 040/** 041 * Base class that can be used to implement a standard {@link javax.websocket.Encoder} 042 * and/or {@link javax.websocket.Decoder}. It provides encode and decode method 043 * implementations that delegate to a Spring {@link ConversionService}. 044 * 045 * <p>By default, this class looks up a {@link ConversionService} registered in the 046 * {@link #getApplicationContext() active ApplicationContext} under 047 * the name {@code 'webSocketConversionService'}. This works fine for both client 048 * and server endpoints, in a Servlet container environment. If not running in a 049 * Servlet container, subclasses will need to override the 050 * {@link #getConversionService()} method to provide an alternative lookup strategy. 051 * 052 * <p>Subclasses can extend this class and should also implement one or 053 * both of {@link javax.websocket.Encoder} and {@link javax.websocket.Decoder}. 054 * For convenience {@link ConvertingEncoderDecoderSupport.BinaryEncoder}, 055 * {@link ConvertingEncoderDecoderSupport.BinaryDecoder}, 056 * {@link ConvertingEncoderDecoderSupport.TextEncoder} and 057 * {@link ConvertingEncoderDecoderSupport.TextDecoder} subclasses are provided. 058 * 059 * <p>Since JSR-356 only allows Encoder/Decoder to be registered by type, instances 060 * of this class are therefore managed by the WebSocket runtime, and do not need to 061 * be registered as Spring Beans. They can, however, by injected with Spring-managed 062 * dependencies via {@link Autowired @Autowire}. 063 * 064 * <p>Converters to convert between the {@link #getType() type} and {@code String} or 065 * {@code ByteBuffer} should be registered. 066 * 067 * @author Phillip Webb 068 * @since 4.0 069 * @param <T> the type being converted to (for Encoder) or from (for Decoder) 070 * @param <M> the WebSocket message type ({@link String} or {@link ByteBuffer}) 071 * @see ConvertingEncoderDecoderSupport.BinaryEncoder 072 * @see ConvertingEncoderDecoderSupport.BinaryDecoder 073 * @see ConvertingEncoderDecoderSupport.TextEncoder 074 * @see ConvertingEncoderDecoderSupport.TextDecoder 075 */ 076public abstract class ConvertingEncoderDecoderSupport<T, M> { 077 078 private static final String CONVERSION_SERVICE_BEAN_NAME = "webSocketConversionService"; 079 080 081 /** 082 * Called to initialize the encoder/decoder. 083 * @see javax.websocket.Encoder#init(EndpointConfig) 084 * @see javax.websocket.Decoder#init(EndpointConfig) 085 */ 086 public void init(EndpointConfig config) { 087 ApplicationContext applicationContext = getApplicationContext(); 088 if (applicationContext instanceof ConfigurableApplicationContext) { 089 ConfigurableListableBeanFactory beanFactory = 090 ((ConfigurableApplicationContext) applicationContext).getBeanFactory(); 091 beanFactory.autowireBean(this); 092 } 093 } 094 095 /** 096 * Called to destroy the encoder/decoder. 097 * @see javax.websocket.Encoder#destroy() 098 * @see javax.websocket.Decoder#destroy() 099 */ 100 public void destroy() { 101 } 102 103 /** 104 * Strategy method used to obtain the {@link ConversionService}. By default this 105 * method expects a bean named {@code 'webSocketConversionService'} in the 106 * {@link #getApplicationContext() active ApplicationContext}. 107 * @return the {@link ConversionService} (never null) 108 */ 109 protected ConversionService getConversionService() { 110 ApplicationContext applicationContext = getApplicationContext(); 111 Assert.state(applicationContext != null, "Unable to locate the Spring ApplicationContext"); 112 try { 113 return applicationContext.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class); 114 } 115 catch (BeansException ex) { 116 throw new IllegalStateException("Unable to find ConversionService: please configure a '" + 117 CONVERSION_SERVICE_BEAN_NAME + "' or override the getConversionService() method", ex); 118 } 119 } 120 121 /** 122 * Returns the active {@link ApplicationContext}. Be default this method obtains 123 * the context via {@link ContextLoader#getCurrentWebApplicationContext()}, which 124 * finds the ApplicationContext loaded via {@link ContextLoader} typically in a 125 * Servlet container environment. When not running in a Servlet container and 126 * not using {@link ContextLoader}, this method should be overridden. 127 * @return the {@link ApplicationContext} or {@code null} 128 */ 129 @Nullable 130 protected ApplicationContext getApplicationContext() { 131 return ContextLoader.getCurrentWebApplicationContext(); 132 } 133 134 /** 135 * Returns the type being converted. By default the type is resolved using 136 * the generic arguments of the class. 137 */ 138 protected TypeDescriptor getType() { 139 return TypeDescriptor.valueOf(resolveTypeArguments()[0]); 140 } 141 142 /** 143 * Returns the websocket message type. By default the type is resolved using 144 * the generic arguments of the class. 145 */ 146 protected TypeDescriptor getMessageType() { 147 return TypeDescriptor.valueOf(resolveTypeArguments()[1]); 148 } 149 150 private Class<?>[] resolveTypeArguments() { 151 Class<?>[] resolved = GenericTypeResolver.resolveTypeArguments(getClass(), ConvertingEncoderDecoderSupport.class); 152 if (resolved == null) { 153 throw new IllegalStateException("ConvertingEncoderDecoderSupport's generic types T and M " + 154 "need to be substituted in subclass: " + getClass()); 155 } 156 return resolved; 157 } 158 159 /** 160 * Encode an object to a message. 161 * @see javax.websocket.Encoder.Text#encode(Object) 162 * @see javax.websocket.Encoder.Binary#encode(Object) 163 */ 164 @SuppressWarnings("unchecked") 165 @Nullable 166 public M encode(T object) throws EncodeException { 167 try { 168 return (M) getConversionService().convert(object, getType(), getMessageType()); 169 } 170 catch (ConversionException ex) { 171 throw new EncodeException(object, "Unable to encode websocket message using ConversionService", ex); 172 } 173 } 174 175 /** 176 * Determine if a given message can be decoded. 177 * @see #decode(Object) 178 * @see javax.websocket.Decoder.Text#willDecode(String) 179 * @see javax.websocket.Decoder.Binary#willDecode(ByteBuffer) 180 */ 181 public boolean willDecode(M bytes) { 182 return getConversionService().canConvert(getType(), getMessageType()); 183 } 184 185 /** 186 * Decode the a message into an object. 187 * @see javax.websocket.Decoder.Text#decode(String) 188 * @see javax.websocket.Decoder.Binary#decode(ByteBuffer) 189 */ 190 @SuppressWarnings("unchecked") 191 @Nullable 192 public T decode(M message) throws DecodeException { 193 try { 194 return (T) getConversionService().convert(message, getMessageType(), getType()); 195 } 196 catch (ConversionException ex) { 197 if (message instanceof String) { 198 throw new DecodeException((String) message, 199 "Unable to decode websocket message using ConversionService", ex); 200 } 201 if (message instanceof ByteBuffer) { 202 throw new DecodeException((ByteBuffer) message, 203 "Unable to decode websocket message using ConversionService", ex); 204 } 205 throw ex; 206 } 207 } 208 209 210 /** 211 * A binary {@link javax.websocket.Encoder.Binary javax.websocket.Encoder} that delegates 212 * to Spring's conversion service. See {@link ConvertingEncoderDecoderSupport} for details. 213 * @param <T> the type that this Encoder can convert to 214 */ 215 public abstract static class BinaryEncoder<T> extends ConvertingEncoderDecoderSupport<T, ByteBuffer> 216 implements Encoder.Binary<T> { 217 } 218 219 220 /** 221 * A binary {@link javax.websocket.Encoder.Binary javax.websocket.Encoder} that delegates 222 * to Spring's conversion service. See {@link ConvertingEncoderDecoderSupport} for details. 223 * @param <T> the type that this Decoder can convert from 224 */ 225 public abstract static class BinaryDecoder<T> extends ConvertingEncoderDecoderSupport<T, ByteBuffer> 226 implements Decoder.Binary<T> { 227 } 228 229 230 /** 231 * A text {@link javax.websocket.Encoder.Text javax.websocket.Encoder} that delegates 232 * to Spring's conversion service. See {@link ConvertingEncoderDecoderSupport} for 233 * details. 234 * @param <T> the type that this Encoder can convert to 235 */ 236 public abstract static class TextEncoder<T> extends ConvertingEncoderDecoderSupport<T, String> 237 implements Encoder.Text<T> { 238 } 239 240 241 /** 242 * A Text {@link javax.websocket.Encoder.Text javax.websocket.Encoder} that delegates 243 * to Spring's conversion service. See {@link ConvertingEncoderDecoderSupport} for details. 244 * @param <T> the type that this Decoder can convert from 245 */ 246 public abstract static class TextDecoder<T> extends ConvertingEncoderDecoderSupport<T, String> 247 implements Decoder.Text<T> { 248 } 249 250}