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