001/* 002 * Copyright 2002-2017 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.remoting.caucho; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.UndeclaredThrowableException; 021import java.net.ConnectException; 022import java.net.MalformedURLException; 023 024import com.caucho.hessian.HessianException; 025import com.caucho.hessian.client.HessianConnectionException; 026import com.caucho.hessian.client.HessianConnectionFactory; 027import com.caucho.hessian.client.HessianProxyFactory; 028import com.caucho.hessian.client.HessianRuntimeException; 029import com.caucho.hessian.io.SerializerFactory; 030import org.aopalliance.intercept.MethodInterceptor; 031import org.aopalliance.intercept.MethodInvocation; 032 033import org.springframework.lang.Nullable; 034import org.springframework.remoting.RemoteAccessException; 035import org.springframework.remoting.RemoteConnectFailureException; 036import org.springframework.remoting.RemoteLookupFailureException; 037import org.springframework.remoting.RemoteProxyFailureException; 038import org.springframework.remoting.support.UrlBasedRemoteAccessor; 039import org.springframework.util.Assert; 040 041/** 042 * {@link org.aopalliance.intercept.MethodInterceptor} for accessing a Hessian service. 043 * Supports authentication via username and password. 044 * The service URL must be an HTTP URL exposing a Hessian service. 045 * 046 * <p>Hessian is a slim, binary RPC protocol. 047 * For information on Hessian, see the 048 * <a href="http://hessian.caucho.com">Hessian website</a> 049 * <b>Note: As of Spring 4.0, this client requires Hessian 4.0 or above.</b> 050 * 051 * <p>Note: There is no requirement for services accessed with this proxy factory 052 * to have been exported using Spring's {@link HessianServiceExporter}, as there is 053 * no special handling involved. As a consequence, you can also access services that 054 * have been exported using Caucho's {@link com.caucho.hessian.server.HessianServlet}. 055 * 056 * @author Juergen Hoeller 057 * @since 29.09.2003 058 * @see #setServiceInterface 059 * @see #setServiceUrl 060 * @see #setUsername 061 * @see #setPassword 062 * @see HessianServiceExporter 063 * @see HessianProxyFactoryBean 064 * @see com.caucho.hessian.client.HessianProxyFactory 065 * @see com.caucho.hessian.server.HessianServlet 066 */ 067public class HessianClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor { 068 069 private HessianProxyFactory proxyFactory = new HessianProxyFactory(); 070 071 @Nullable 072 private Object hessianProxy; 073 074 075 /** 076 * Set the HessianProxyFactory instance to use. 077 * If not specified, a default HessianProxyFactory will be created. 078 * <p>Allows to use an externally configured factory instance, 079 * in particular a custom HessianProxyFactory subclass. 080 */ 081 public void setProxyFactory(@Nullable HessianProxyFactory proxyFactory) { 082 this.proxyFactory = (proxyFactory != null ? proxyFactory : new HessianProxyFactory()); 083 } 084 085 /** 086 * Specify the Hessian SerializerFactory to use. 087 * <p>This will typically be passed in as an inner bean definition 088 * of type {@code com.caucho.hessian.io.SerializerFactory}, 089 * with custom bean property values applied. 090 */ 091 public void setSerializerFactory(SerializerFactory serializerFactory) { 092 this.proxyFactory.setSerializerFactory(serializerFactory); 093 } 094 095 /** 096 * Set whether to send the Java collection type for each serialized 097 * collection. Default is "true". 098 */ 099 public void setSendCollectionType(boolean sendCollectionType) { 100 this.proxyFactory.getSerializerFactory().setSendCollectionType(sendCollectionType); 101 } 102 103 /** 104 * Set whether to allow non-serializable types as Hessian arguments 105 * and return values. Default is "true". 106 */ 107 public void setAllowNonSerializable(boolean allowNonSerializable) { 108 this.proxyFactory.getSerializerFactory().setAllowNonSerializable(allowNonSerializable); 109 } 110 111 /** 112 * Set whether overloaded methods should be enabled for remote invocations. 113 * Default is "false". 114 * @see com.caucho.hessian.client.HessianProxyFactory#setOverloadEnabled 115 */ 116 public void setOverloadEnabled(boolean overloadEnabled) { 117 this.proxyFactory.setOverloadEnabled(overloadEnabled); 118 } 119 120 /** 121 * Set the username that this factory should use to access the remote service. 122 * Default is none. 123 * <p>The username will be sent by Hessian via HTTP Basic Authentication. 124 * @see com.caucho.hessian.client.HessianProxyFactory#setUser 125 */ 126 public void setUsername(String username) { 127 this.proxyFactory.setUser(username); 128 } 129 130 /** 131 * Set the password that this factory should use to access the remote service. 132 * Default is none. 133 * <p>The password will be sent by Hessian via HTTP Basic Authentication. 134 * @see com.caucho.hessian.client.HessianProxyFactory#setPassword 135 */ 136 public void setPassword(String password) { 137 this.proxyFactory.setPassword(password); 138 } 139 140 /** 141 * Set whether Hessian's debug mode should be enabled. 142 * Default is "false". 143 * @see com.caucho.hessian.client.HessianProxyFactory#setDebug 144 */ 145 public void setDebug(boolean debug) { 146 this.proxyFactory.setDebug(debug); 147 } 148 149 /** 150 * Set whether to use a chunked post for sending a Hessian request. 151 * @see com.caucho.hessian.client.HessianProxyFactory#setChunkedPost 152 */ 153 public void setChunkedPost(boolean chunkedPost) { 154 this.proxyFactory.setChunkedPost(chunkedPost); 155 } 156 157 /** 158 * Specify a custom HessianConnectionFactory to use for the Hessian client. 159 */ 160 public void setConnectionFactory(HessianConnectionFactory connectionFactory) { 161 this.proxyFactory.setConnectionFactory(connectionFactory); 162 } 163 164 /** 165 * Set the socket connect timeout to use for the Hessian client. 166 * @see com.caucho.hessian.client.HessianProxyFactory#setConnectTimeout 167 */ 168 public void setConnectTimeout(long timeout) { 169 this.proxyFactory.setConnectTimeout(timeout); 170 } 171 172 /** 173 * Set the timeout to use when waiting for a reply from the Hessian service. 174 * @see com.caucho.hessian.client.HessianProxyFactory#setReadTimeout 175 */ 176 public void setReadTimeout(long timeout) { 177 this.proxyFactory.setReadTimeout(timeout); 178 } 179 180 /** 181 * Set whether version 2 of the Hessian protocol should be used for 182 * parsing requests and replies. Default is "false". 183 * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request 184 */ 185 public void setHessian2(boolean hessian2) { 186 this.proxyFactory.setHessian2Request(hessian2); 187 this.proxyFactory.setHessian2Reply(hessian2); 188 } 189 190 /** 191 * Set whether version 2 of the Hessian protocol should be used for 192 * parsing requests. Default is "false". 193 * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Request 194 */ 195 public void setHessian2Request(boolean hessian2) { 196 this.proxyFactory.setHessian2Request(hessian2); 197 } 198 199 /** 200 * Set whether version 2 of the Hessian protocol should be used for 201 * parsing replies. Default is "false". 202 * @see com.caucho.hessian.client.HessianProxyFactory#setHessian2Reply 203 */ 204 public void setHessian2Reply(boolean hessian2) { 205 this.proxyFactory.setHessian2Reply(hessian2); 206 } 207 208 209 @Override 210 public void afterPropertiesSet() { 211 super.afterPropertiesSet(); 212 prepare(); 213 } 214 215 /** 216 * Initialize the Hessian proxy for this interceptor. 217 * @throws RemoteLookupFailureException if the service URL is invalid 218 */ 219 public void prepare() throws RemoteLookupFailureException { 220 try { 221 this.hessianProxy = createHessianProxy(this.proxyFactory); 222 } 223 catch (MalformedURLException ex) { 224 throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex); 225 } 226 } 227 228 /** 229 * Create the Hessian proxy that is wrapped by this interceptor. 230 * @param proxyFactory the proxy factory to use 231 * @return the Hessian proxy 232 * @throws MalformedURLException if thrown by the proxy factory 233 * @see com.caucho.hessian.client.HessianProxyFactory#create 234 */ 235 protected Object createHessianProxy(HessianProxyFactory proxyFactory) throws MalformedURLException { 236 Assert.notNull(getServiceInterface(), "'serviceInterface' is required"); 237 return proxyFactory.create(getServiceInterface(), getServiceUrl(), getBeanClassLoader()); 238 } 239 240 241 @Override 242 @Nullable 243 public Object invoke(MethodInvocation invocation) throws Throwable { 244 if (this.hessianProxy == null) { 245 throw new IllegalStateException("HessianClientInterceptor is not properly initialized - " + 246 "invoke 'prepare' before attempting any operations"); 247 } 248 249 ClassLoader originalClassLoader = overrideThreadContextClassLoader(); 250 try { 251 return invocation.getMethod().invoke(this.hessianProxy, invocation.getArguments()); 252 } 253 catch (InvocationTargetException ex) { 254 Throwable targetEx = ex.getTargetException(); 255 // Hessian 4.0 check: another layer of InvocationTargetException. 256 if (targetEx instanceof InvocationTargetException) { 257 targetEx = ((InvocationTargetException) targetEx).getTargetException(); 258 } 259 if (targetEx instanceof HessianConnectionException) { 260 throw convertHessianAccessException(targetEx); 261 } 262 else if (targetEx instanceof HessianException || targetEx instanceof HessianRuntimeException) { 263 Throwable cause = targetEx.getCause(); 264 throw convertHessianAccessException(cause != null ? cause : targetEx); 265 } 266 else if (targetEx instanceof UndeclaredThrowableException) { 267 UndeclaredThrowableException utex = (UndeclaredThrowableException) targetEx; 268 throw convertHessianAccessException(utex.getUndeclaredThrowable()); 269 } 270 else { 271 throw targetEx; 272 } 273 } 274 catch (Throwable ex) { 275 throw new RemoteProxyFailureException( 276 "Failed to invoke Hessian proxy for remote service [" + getServiceUrl() + "]", ex); 277 } 278 finally { 279 resetThreadContextClassLoader(originalClassLoader); 280 } 281 } 282 283 /** 284 * Convert the given Hessian access exception to an appropriate 285 * Spring RemoteAccessException. 286 * @param ex the exception to convert 287 * @return the RemoteAccessException to throw 288 */ 289 protected RemoteAccessException convertHessianAccessException(Throwable ex) { 290 if (ex instanceof HessianConnectionException || ex instanceof ConnectException) { 291 return new RemoteConnectFailureException( 292 "Cannot connect to Hessian remote service at [" + getServiceUrl() + "]", ex); 293 } 294 else { 295 return new RemoteAccessException( 296 "Cannot access Hessian remote service at [" + getServiceUrl() + "]", ex); 297 } 298 } 299 300}